merged arg casting

This commit is contained in:
ub 2017-04-03 10:15:27 +02:00
commit 6da67fa249
25 changed files with 1496 additions and 152 deletions

2
.gitignore vendored
View File

@ -1,3 +1,3 @@
.*
!.git*
/vendor/

View File

@ -1,3 +1,8 @@
.PHONY: demo
demo:
cd demo && gotsrpc -skipgotsrpc config.yml
.PHONY: install
install:
GOBIN=/usr/local/bin go install cmd/gotsrpc/gotsrpc.go

View File

@ -7,11 +7,11 @@ import (
"io/ioutil"
"os"
"path"
"path/filepath"
"runtime"
"sort"
"strings"
"path/filepath"
"github.com/foomo/gotsrpc/config"
)
@ -55,26 +55,36 @@ func Build(conf *config.Config, goPath string) {
mappedTypeScript := map[string]map[string]*code{}
for name, target := range conf.Targets {
fmt.Fprintln(os.Stderr, "building target", name)
longPackageName := target.Package
longPackageNameParts := strings.Split(longPackageName, "/")
goFilename := path.Join(goPath, "src", longPackageName, "gotsrpc.go")
goRPCProxiesFilename := path.Join(goPath, "src", longPackageName, "gorpc.go")
goRPCClientsFilename := path.Join(goPath, "src", longPackageName, "gorpcclient.go")
goTSRPCProxiesFilename := path.Join(goPath, "src", longPackageName, "gotsrpc.go")
goTSRPCClientsFilename := path.Join(goPath, "src", longPackageName, "gotsrpcclient.go")
_, err := os.Stat(goFilename)
if err == nil {
fmt.Fprintln(os.Stderr, " removing existing", goFilename)
os.Remove(goFilename)
remove := func(filename string) {
_, err := os.Stat(filename)
if err == nil {
fmt.Fprintln(os.Stderr, " removing existing", filename)
os.Remove(filename)
}
}
remove(goRPCProxiesFilename)
remove(goRPCClientsFilename)
remove(goTSRPCProxiesFilename)
remove(goTSRPCClientsFilename)
packageName := longPackageNameParts[len(longPackageNameParts)-1]
services, structs, scalarTypes, constants, err := Read(goPath, longPackageName, target.Services)
goPaths := []string{goPath, runtime.GOROOT()}
services, structs, scalarTypes, constants, err := Read(goPaths, longPackageName, target.Services)
if err != nil {
fmt.Fprintln(os.Stderr, " an error occured while trying to understand your code", err)
os.Exit(2)
}
ts, err := RenderTypeScriptServices(conf.ModuleKind, services, conf.Mappings, scalarTypes, target.TypeScriptModule)
ts, err := RenderTypeScriptServices(conf.ModuleKind, services, conf.Mappings, scalarTypes, target)
if err != nil {
fmt.Fprintln(os.Stderr, " could not generate ts code", err)
os.Exit(3)
@ -98,25 +108,64 @@ func Build(conf *config.Config, goPath string) {
os.Exit(4)
}
gocode, goerr := RenderGo(services, longPackageName, packageName)
if goerr != nil {
fmt.Fprintln(os.Stderr, " could not generate go code in target", name, goerr)
os.Exit(4)
formatAndWrite := func(code string, filename string) {
formattedGoBytes, formattingError := format.Source([]byte(code))
if formattingError == nil {
code = string(formattedGoBytes)
} else {
fmt.Fprintln(os.Stderr, " could not format go ts rpc proxies code", formattingError)
}
writeErr := ioutil.WriteFile(filename, []byte(code), 0644)
if writeErr != nil {
fmt.Fprintln(os.Stderr, " could not write go source to file", writeErr)
os.Exit(5)
}
}
if len(target.TSRPC) > 0 {
goTSRPCProxiesCode, goerr := RenderGoTSRPCProxies(services, longPackageName, packageName, target)
if goerr != nil {
fmt.Fprintln(os.Stderr, " could not generate go ts rpc proxies code in target", name, goerr)
os.Exit(4)
}
formatAndWrite(goTSRPCProxiesCode, goTSRPCProxiesFilename)
goTSRPCClientsCode, goerr := RenderGoTSRPCClients(services, longPackageName, packageName, target)
if goerr != nil {
fmt.Fprintln(os.Stderr, " could not generate go ts rpc clients code in target", name, goerr)
os.Exit(4)
}
formatAndWrite(goTSRPCClientsCode, goTSRPCClientsFilename)
}
formattedGoBytes, formattingError := format.Source([]byte(gocode))
if formattingError == nil {
gocode = string(formattedGoBytes)
} else {
fmt.Fprintln(os.Stderr, " could not format go code", formattingError)
if len(target.GoRPC) > 0 {
goRPCProxiesCode, goerr := RenderGoRPCProxies(services, longPackageName, packageName, target)
if goerr != nil {
fmt.Fprintln(os.Stderr, " could not generate go rpc proxies code in target", name, goerr)
os.Exit(4)
}
formatAndWrite(goRPCProxiesCode, goRPCProxiesFilename)
goRPCClientsCode, goerr := RenderGoRPCClients(services, longPackageName, packageName, target)
if goerr != nil {
fmt.Fprintln(os.Stderr, " could not generate go rpc clients code in target", name, goerr)
os.Exit(4)
}
formatAndWrite(goRPCClientsCode, goRPCClientsFilename)
}
writeErr := ioutil.WriteFile(goFilename, []byte(gocode), 0644)
if writeErr != nil {
fmt.Fprintln(os.Stderr, " could not write go source to file", writeErr)
os.Exit(5)
if len(target.PHPRPC) > 0 {
phpRPCClientsCode, goerr := RenderPHPRPCClients(services, target)
if goerr != nil {
fmt.Fprintln(os.Stderr, " could not generate php rpc clients code in target", name, goerr)
os.Exit(4)
}
for filename, code := range phpRPCClientsCode {
updateCode(filename, code)
}
}
}
// spew.Dump(mappedTypeScript)
for goPackage, mappedStructsMap := range mappedTypeScript {
mapping, ok := conf.Mappings[goPackage]

50
client.go Normal file
View File

@ -0,0 +1,50 @@
package gotsrpc
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
)
// ClientTransport to use for calls
var ClientTransport = &http.Transport{}
// CallClient calls a method on the remove service
func CallClient(url string, endpoint string, method string, args []interface{}, reply []interface{}) error {
// Marshall args
jsonArgs := []string{}
for _, value := range args {
jsonArg, err := json.Marshal(value)
if err != nil {
return err
}
jsonArgs = append(jsonArgs, string(jsonArg))
}
// Create request
request := "[" + strings.Join(jsonArgs, ",") + "]"
// Create post url
postURL := fmt.Sprintf("%s%s/%s", url, endpoint, method)
// Post
client := &http.Client{Transport: ClientTransport}
resp, err := client.Post(postURL, "application/json", strings.NewReader(request))
if err != nil {
return err
}
defer resp.Body.Close()
// Read in body
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
// Check status
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("%s: %s", resp.Status, string(body))
}
// Unmarshal reply
if err := json.Unmarshal(body, &reply); err != nil {
return err
}
return nil
}

View File

@ -39,7 +39,7 @@ func serveFile(name string, w http.ResponseWriter) {
func main() {
d := &Demo{
proxy: demo.NewServiceGoTSRPCProxy(&demo.Service{}, "/service"),
proxy: demo.NewServiceGoTSRPCProxy(&demo.Demo{}, "/service"),
}
fmt.Println(http.ListenAndServe(":8080", d))
}

View File

@ -8,11 +8,52 @@ import (
"gopkg.in/yaml.v2"
)
type PHPTarget struct {
Out string `yaml:"out"`
Namespace string `yaml:"namespace"`
}
type Target struct {
Package string
Services map[string]string
TypeScriptModule string `yaml:"module"`
Out string
Package string `yaml:"package"`
Services map[string]string `yaml:"services"`
TypeScriptModule string `yaml:"module"`
Out string `yaml:"out"`
GoRPC []string `yaml:"gorpc"`
TSRPC []string `yaml:"tsrpc"`
PHPRPC map[string]*PHPTarget `yaml:"phprpc"`
}
func (t *Target) IsGoRPC(service string) bool {
for _, value := range t.GoRPC {
if value == service {
return true
}
}
return false
}
func (t *Target) IsTSRPC(service string) bool {
if len(t.TSRPC) == 0 {
return true
}
for _, value := range t.TSRPC {
if value == service {
return true
}
}
return false
}
func (t *Target) IsPHPRPC(service string) bool {
if len(t.PHPRPC) == 0 {
return false
}
_, ok := t.PHPRPC[service]
return ok
}
func (t *Target) GetPHPTarget(service string) *PHPTarget {
return t.PHPRPC[service]
}
type Mapping struct {

View File

@ -3,9 +3,14 @@ targets:
demo:
module: GoTSRPC.Demo
services:
- Service
/service/foo: Foo
/service/demo: Demo
package: github.com/foomo/gotsrpc/demo
out: /tmp/test.ts
out: /tmp/test.ts
gorpc:
- Foo
- Demo
mappings:
github.com/foomo/gotsrpc/demo:
module: GoTSRPC.Demo

View File

@ -6,21 +6,17 @@ type Err struct {
type ScalarInPlace string
type Service struct {
type Demo struct {
Bla bool
}
func (s *Service) Hello(name string) (reply string, err *Err) {
func (d *Demo) Hello(name string) (reply string, err *Err) {
if name == "Peter" {
return "", &Err{"fuck you Peter I do not like you"}
}
return "Hello from the server: " + name, nil
}
func sepp(bar bool) string {
return "ich bin der sepp"
}
func (s *Service) nothingInNothinOut() {
func (d *Demo) nothingInNothinOut() {
}

View File

@ -1,8 +1,6 @@
package demo
import "github.com/foomo/gotsrpc/demo/nested"
//import nstd "github.com/foomo/gotsrpc/demo/nested"
import nstd "github.com/foomo/gotsrpc/demo/nested"
type Address struct {
City string `json:"city,omitempty"`
@ -13,8 +11,8 @@ type Address struct {
ArrayArrayAddress [][]*Address
People []Person
MapCrap map[string]map[int]bool
NestedPtr *nested.Nested
NestedStruct nested.Nested
NestedPtr *nstd.Nested
NestedStruct nstd.Nested
}
type Person struct {
@ -32,22 +30,26 @@ type Person struct {
iAmPrivate string
}
func (s *Service) ExtractAddress(person *Person) (addr *Address, e *Err) {
func (d *Demo) ExtractAddress(person *Person) (addr *Address, e *Err) {
if person.AddressPtr != nil {
return person.AddressPtr, nil
}
return nil, &Err{"there is no address on that person"}
}
func (s *Service) TestScalarInPlace() ScalarInPlace {
func (d *Demo) TestScalarInPlace() ScalarInPlace {
return ScalarInPlace("hier")
}
func (s *Service) Nest() *nested.Nested {
func (d *Demo) MapCrap() (crap map[string][]int) {
return map[string][]int{}
}
func (d *Demo) Nest() *nstd.Nested {
return nil
}
//func (s *Service) GiveMeAScalar() (amount nstd.Amount, wahr nstd.True, hier ScalarInPlace) {
func (s *Service) giveMeAScalar() (amount nested.Amount, wahr nested.True, hier ScalarInPlace) {
return nested.Amount(10), nested.ItIsTrue, ScalarInPlace("hier")
func (d *Demo) GiveMeAScalar() (amount nstd.Amount, wahr nstd.True, hier ScalarInPlace) {
//func (s *Service) giveMeAScalar() (amount nstd.Amount, wahr nstd.True, hier ScalarInPlace) {
return nstd.Amount(10), nstd.ItIsTrue, ScalarInPlace("hier")
}

223
demo/gorpc.go Normal file
View File

@ -0,0 +1,223 @@
// this file was auto generated by gotsrpc https://github.com/foomo/gotsrpc
package demo
import (
tls "crypto/tls"
gob "encoding/gob"
fmt "fmt"
gotsrpc "github.com/foomo/gotsrpc"
nested "github.com/foomo/gotsrpc/demo/nested"
gorpc "github.com/valyala/gorpc"
reflect "reflect"
strings "strings"
time "time"
)
type (
FooGoRPCProxy struct {
server *gorpc.Server
service *Foo
callStatsHandler gotsrpc.GoRPCCallStatsHandlerFun
}
HelloRequest struct {
Number int64
}
HelloResponse struct {
RetHello_0 int
}
)
func init() {
gob.Register(HelloRequest{})
gob.Register(HelloResponse{})
}
func NewFooGoRPCProxy(addr string, service *Foo, tlsConfig *tls.Config) *FooGoRPCProxy {
proxy := &FooGoRPCProxy{
service: service,
}
if tlsConfig != nil {
proxy.server = gorpc.NewTLSServer(addr, proxy.handler, tlsConfig)
} else {
proxy.server = gorpc.NewTCPServer(addr, proxy.handler)
}
return proxy
}
func (p *FooGoRPCProxy) Start() error {
return p.server.Start()
}
func (p *FooGoRPCProxy) Stop() {
p.server.Stop()
}
func (p *FooGoRPCProxy) SetCallStatsHandler(handler gotsrpc.GoRPCCallStatsHandlerFun) {
p.callStatsHandler = handler
}
func (p *FooGoRPCProxy) handler(clientAddr string, request interface{}) (response interface{}) {
start := time.Now()
reqType := reflect.TypeOf(request).String()
funcNameParts := strings.Split(reqType, ".")
funcName := funcNameParts[len(funcNameParts)-1]
switch funcName {
case "HelloRequest":
req := request.(HelloRequest)
retHello_0 := p.service.Hello(req.Number)
response = HelloResponse{RetHello_0: retHello_0}
default:
fmt.Println("Unkown request type", reflect.TypeOf(request).String())
}
if p.callStatsHandler != nil {
p.callStatsHandler(&gotsrpc.CallStats{
Func: funcName,
Package: "github.com/foomo/gotsrpc/demo",
Service: "Foo",
Execution: time.Since(start),
})
}
return
}
type (
DemoGoRPCProxy struct {
server *gorpc.Server
service *Demo
callStatsHandler gotsrpc.GoRPCCallStatsHandlerFun
}
ExtractAddressRequest struct {
Person *Person
}
ExtractAddressResponse struct {
Addr *Address
E *Err
}
GiveMeAScalarRequest struct {
}
GiveMeAScalarResponse struct {
Amount nested.Amount
Wahr nested.True
Hier ScalarInPlace
}
HelloRequest struct {
Name string
}
HelloResponse struct {
Reply string
Err *Err
}
MapCrapRequest struct {
}
MapCrapResponse struct {
Crap map[string][]int
}
NestRequest struct {
}
NestResponse struct {
RetNest_0 *nested.Nested
}
TestScalarInPlaceRequest struct {
}
TestScalarInPlaceResponse struct {
RetTestScalarInPlace_0 ScalarInPlace
}
)
func init() {
gob.Register(ExtractAddressRequest{})
gob.Register(ExtractAddressResponse{})
gob.Register(GiveMeAScalarRequest{})
gob.Register(GiveMeAScalarResponse{})
gob.Register(HelloRequest{})
gob.Register(HelloResponse{})
gob.Register(MapCrapRequest{})
gob.Register(MapCrapResponse{})
gob.Register(NestRequest{})
gob.Register(NestResponse{})
gob.Register(TestScalarInPlaceRequest{})
gob.Register(TestScalarInPlaceResponse{})
}
func NewDemoGoRPCProxy(addr string, service *Demo, tlsConfig *tls.Config) *DemoGoRPCProxy {
proxy := &DemoGoRPCProxy{
service: service,
}
if tlsConfig != nil {
proxy.server = gorpc.NewTLSServer(addr, proxy.handler, tlsConfig)
} else {
proxy.server = gorpc.NewTCPServer(addr, proxy.handler)
}
return proxy
}
func (p *DemoGoRPCProxy) Start() error {
return p.server.Start()
}
func (p *DemoGoRPCProxy) Stop() {
p.server.Stop()
}
func (p *DemoGoRPCProxy) SetCallStatsHandler(handler gotsrpc.GoRPCCallStatsHandlerFun) {
p.callStatsHandler = handler
}
func (p *DemoGoRPCProxy) handler(clientAddr string, request interface{}) (response interface{}) {
start := time.Now()
reqType := reflect.TypeOf(request).String()
funcNameParts := strings.Split(reqType, ".")
funcName := funcNameParts[len(funcNameParts)-1]
switch funcName {
case "ExtractAddressRequest":
req := request.(ExtractAddressRequest)
addr, e := p.service.ExtractAddress(req.Person)
response = ExtractAddressResponse{Addr: addr, E: e}
case "GiveMeAScalarRequest":
amount, wahr, hier := p.service.GiveMeAScalar()
response = GiveMeAScalarResponse{Amount: amount, Wahr: wahr, Hier: hier}
case "HelloRequest":
req := request.(HelloRequest)
reply, err := p.service.Hello(req.Name)
response = HelloResponse{Reply: reply, Err: err}
case "MapCrapRequest":
crap := p.service.MapCrap()
response = MapCrapResponse{Crap: crap}
case "NestRequest":
retNest_0 := p.service.Nest()
response = NestResponse{RetNest_0: retNest_0}
case "TestScalarInPlaceRequest":
retTestScalarInPlace_0 := p.service.TestScalarInPlace()
response = TestScalarInPlaceResponse{RetTestScalarInPlace_0: retTestScalarInPlace_0}
default:
fmt.Println("Unkown request type", reflect.TypeOf(request).String())
}
if p.callStatsHandler != nil {
p.callStatsHandler(&gotsrpc.CallStats{
Func: funcName,
Package: "github.com/foomo/gotsrpc/demo",
Service: "Demo",
Execution: time.Since(start),
})
}
return
}

131
demo/gorpcclient.go Normal file
View File

@ -0,0 +1,131 @@
// this file was auto generated by gotsrpc https://github.com/foomo/gotsrpc
package demo
import (
tls "crypto/tls"
nested "github.com/foomo/gotsrpc/demo/nested"
gorpc "github.com/valyala/gorpc"
)
type FooGoRPCClient struct {
client *gorpc.Client
}
func NewFooGoRPCClient(addr string, tlsConfig *tls.Config) *FooGoRPCClient {
client := &FooGoRPCClient{}
if tlsConfig == nil {
client.client = gorpc.NewTCPClient(addr)
} else {
client.client = gorpc.NewTLSClient(addr, tlsConfig)
}
client.Start()
return client
}
func (c *FooGoRPCClient) Start() {
c.client.Start()
}
func (c *FooGoRPCClient) Stop() {
c.client.Stop()
}
func (c *FooGoRPCClient) Hello(number int64) (retHello_0 int, clientErr error) {
req := HelloRequest{Number: number}
res, err := c.client.Call(req)
if err != nil {
clientErr = err
return
}
response := res.(HelloResponse)
return response.RetHello_0, nil
}
type DemoGoRPCClient struct {
client *gorpc.Client
}
func NewDemoGoRPCClient(addr string, tlsConfig *tls.Config) *DemoGoRPCClient {
client := &DemoGoRPCClient{}
if tlsConfig == nil {
client.client = gorpc.NewTCPClient(addr)
} else {
client.client = gorpc.NewTLSClient(addr, tlsConfig)
}
client.Start()
return client
}
func (c *DemoGoRPCClient) Start() {
c.client.Start()
}
func (c *DemoGoRPCClient) Stop() {
c.client.Stop()
}
func (c *DemoGoRPCClient) ExtractAddress(person *Person) (addr *Address, e *Err, clientErr error) {
req := ExtractAddressRequest{Person: person}
res, err := c.client.Call(req)
if err != nil {
clientErr = err
return
}
response := res.(ExtractAddressResponse)
return response.Addr, response.E, nil
}
func (c *DemoGoRPCClient) GiveMeAScalar() (amount nested.Amount, wahr nested.True, hier ScalarInPlace, clientErr error) {
req := GiveMeAScalarRequest{}
res, err := c.client.Call(req)
if err != nil {
clientErr = err
return
}
response := res.(GiveMeAScalarResponse)
return response.Amount, response.Wahr, response.Hier, nil
}
func (c *DemoGoRPCClient) Hello(name string) (reply string, err *Err, clientErr error) {
req := HelloRequest{Name: name}
res, err := c.client.Call(req)
if err != nil {
clientErr = err
return
}
response := res.(HelloResponse)
return response.Reply, response.Err, nil
}
func (c *DemoGoRPCClient) MapCrap() (crap map[string][]int, clientErr error) {
req := MapCrapRequest{}
res, err := c.client.Call(req)
if err != nil {
clientErr = err
return
}
response := res.(MapCrapResponse)
return response.Crap, nil
}
func (c *DemoGoRPCClient) Nest() (retNest_0 *nested.Nested, clientErr error) {
req := NestRequest{}
res, err := c.client.Call(req)
if err != nil {
clientErr = err
return
}
response := res.(NestResponse)
return response.RetNest_0, nil
}
func (c *DemoGoRPCClient) TestScalarInPlace() (retTestScalarInPlace_0 ScalarInPlace, clientErr error) {
req := TestScalarInPlaceRequest{}
res, err := c.client.Call(req)
if err != nil {
clientErr = err
return
}
response := res.(TestScalarInPlaceResponse)
return response.RetTestScalarInPlace_0, nil
}

View File

@ -4,16 +4,25 @@ package demo
import (
gotsrpc "github.com/foomo/gotsrpc"
http "net/http"
time "time"
)
type ServiceGoTSRPCProxy struct {
type FooGoTSRPCProxy struct {
EndPoint string
allowOrigin []string
service *Service
service *Foo
}
func NewServiceGoTSRPCProxy(service *Service, endpoint string, allowOrigin []string) *ServiceGoTSRPCProxy {
return &ServiceGoTSRPCProxy{
func NewDefaultFooGoTSRPCProxy(service *Foo, allowOrigin []string) *FooGoTSRPCProxy {
return &FooGoTSRPCProxy{
EndPoint: "/service/foo",
allowOrigin: allowOrigin,
service: service,
}
}
func NewFooGoTSRPCProxy(service *Foo, endpoint string, allowOrigin []string) *FooGoTSRPCProxy {
return &FooGoTSRPCProxy{
EndPoint: endpoint,
allowOrigin: allowOrigin,
service: service,
@ -21,46 +30,155 @@ func NewServiceGoTSRPCProxy(service *Service, endpoint string, allowOrigin []str
}
// ServeHTTP exposes your service
func (p *ServiceGoTSRPCProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (p *FooGoTSRPCProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
for _, origin := range p.allowOrigin {
// todo we have to compare this with the referer ... and only send one
w.Header().Add("Access-Control-Allow-Origin", origin)
}
w.Header().Set("Access-Control-Allow-Credentials", "true")
if r.Method != "POST" {
if r.Method != http.MethodPost {
if r.Method == http.MethodOptions {
return
}
gotsrpc.ErrorMethodNotAllowed(w)
return
}
var args []interface{}
switch gotsrpc.GetCalledFunc(r, p.EndPoint) {
funcName := gotsrpc.GetCalledFunc(r, p.EndPoint)
callStats := gotsrpc.GetStatsForRequest(r)
if callStats != nil {
callStats.Func = funcName
callStats.Package = "github.com/foomo/gotsrpc/demo"
callStats.Service = "Foo"
}
switch funcName {
case "Hello":
args = []interface{}{""}
err := gotsrpc.LoadArgs(args, r)
args = []interface{}{int64(0)}
err := gotsrpc.LoadArgs(args, callStats, r)
if err != nil {
gotsrpc.ErrorCouldNotLoadArgs(w)
return
}
helloReply, helloErr := p.service.Hello(args[0].(string))
gotsrpc.Reply([]interface{}{helloReply, helloErr}, w)
return
case "ExtractAddress":
args = []interface{}{&Person{}}
err := gotsrpc.LoadArgs(args, r)
if err != nil {
gotsrpc.ErrorCouldNotLoadArgs(w)
return
executionStart := time.Now()
helloRet := p.service.Hello(int64(args[0].(float64)))
if callStats != nil {
callStats.Execution = time.Now().Sub(executionStart)
}
extractAddressAddr, extractAddressE := p.service.ExtractAddress(args[0].(*Person))
gotsrpc.Reply([]interface{}{extractAddressAddr, extractAddressE}, w)
return
case "TestScalarInPlace":
testScalarInPlaceRet := p.service.TestScalarInPlace()
gotsrpc.Reply([]interface{}{testScalarInPlaceRet}, w)
return
case "Nest":
nestRet := p.service.Nest()
gotsrpc.Reply([]interface{}{nestRet}, w)
gotsrpc.Reply([]interface{}{helloRet}, callStats, r, w)
return
default:
http.Error(w, "404 - not found "+r.URL.Path, http.StatusNotFound)
}
}
type DemoGoTSRPCProxy struct {
EndPoint string
allowOrigin []string
service *Demo
}
func NewDefaultDemoGoTSRPCProxy(service *Demo, allowOrigin []string) *DemoGoTSRPCProxy {
return &DemoGoTSRPCProxy{
EndPoint: "/service/demo",
allowOrigin: allowOrigin,
service: service,
}
}
func NewDemoGoTSRPCProxy(service *Demo, endpoint string, allowOrigin []string) *DemoGoTSRPCProxy {
return &DemoGoTSRPCProxy{
EndPoint: endpoint,
allowOrigin: allowOrigin,
service: service,
}
}
// ServeHTTP exposes your service
func (p *DemoGoTSRPCProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
for _, origin := range p.allowOrigin {
// todo we have to compare this with the referer ... and only send one
w.Header().Add("Access-Control-Allow-Origin", origin)
}
w.Header().Set("Access-Control-Allow-Credentials", "true")
if r.Method != http.MethodPost {
if r.Method == http.MethodOptions {
return
}
gotsrpc.ErrorMethodNotAllowed(w)
return
}
var args []interface{}
funcName := gotsrpc.GetCalledFunc(r, p.EndPoint)
callStats := gotsrpc.GetStatsForRequest(r)
if callStats != nil {
callStats.Func = funcName
callStats.Package = "github.com/foomo/gotsrpc/demo"
callStats.Service = "Demo"
}
switch funcName {
case "ExtractAddress":
args = []interface{}{&Person{}}
err := gotsrpc.LoadArgs(args, callStats, r)
if err != nil {
gotsrpc.ErrorCouldNotLoadArgs(w)
return
}
executionStart := time.Now()
extractAddressAddr, extractAddressE := p.service.ExtractAddress(args[0].(*Person))
if callStats != nil {
callStats.Execution = time.Now().Sub(executionStart)
}
gotsrpc.Reply([]interface{}{extractAddressAddr, extractAddressE}, callStats, r, w)
return
case "GiveMeAScalar":
executionStart := time.Now()
giveMeAScalarAmount, giveMeAScalarWahr, giveMeAScalarHier := p.service.GiveMeAScalar()
if callStats != nil {
callStats.Execution = time.Now().Sub(executionStart)
}
gotsrpc.Reply([]interface{}{giveMeAScalarAmount, giveMeAScalarWahr, giveMeAScalarHier}, callStats, r, w)
return
case "Hello":
args = []interface{}{""}
err := gotsrpc.LoadArgs(args, callStats, r)
if err != nil {
gotsrpc.ErrorCouldNotLoadArgs(w)
return
}
executionStart := time.Now()
helloReply, helloErr := p.service.Hello(args[0].(string))
if callStats != nil {
callStats.Execution = time.Now().Sub(executionStart)
}
gotsrpc.Reply([]interface{}{helloReply, helloErr}, callStats, r, w)
return
case "MapCrap":
executionStart := time.Now()
mapCrapCrap := p.service.MapCrap()
if callStats != nil {
callStats.Execution = time.Now().Sub(executionStart)
}
gotsrpc.Reply([]interface{}{mapCrapCrap}, callStats, r, w)
return
case "Nest":
executionStart := time.Now()
nestRet := p.service.Nest()
if callStats != nil {
callStats.Execution = time.Now().Sub(executionStart)
}
gotsrpc.Reply([]interface{}{nestRet}, callStats, r, w)
return
case "TestScalarInPlace":
executionStart := time.Now()
testScalarInPlaceRet := p.service.TestScalarInPlace()
if callStats != nil {
callStats.Execution = time.Now().Sub(executionStart)
}
gotsrpc.Reply([]interface{}{testScalarInPlaceRet}, callStats, r, w)
return
default:
http.Error(w, "404 - not found "+r.URL.Path, http.StatusNotFound)

View File

@ -1 +0,0 @@
package demo_test

88
demo/gotsrpcclient.go Normal file
View File

@ -0,0 +1,88 @@
// this file was auto generated by gotsrpc https://github.com/foomo/gotsrpc
package demo
import (
gotsrpc "github.com/foomo/gotsrpc"
nested "github.com/foomo/gotsrpc/demo/nested"
)
type FooGoTSRPCClient struct {
URL string
EndPoint string
}
func NewDefaultFooGoTSRPCClient(url string) *FooGoTSRPCClient {
return NewFooGoTSRPCClient(url, "/service/foo")
}
func NewFooGoTSRPCClient(url string, endpoint string) *FooGoTSRPCClient {
return &FooGoTSRPCClient{
URL: url,
EndPoint: endpoint,
}
}
func (c *FooGoTSRPCClient) Hello(number int64) (retHello_0 int, clientErr error) {
args := []interface{}{number}
reply := []interface{}{&retHello_0}
clientErr = gotsrpc.CallClient(c.URL, c.EndPoint, "Hello", args, reply)
return
}
type DemoGoTSRPCClient struct {
URL string
EndPoint string
}
func NewDefaultDemoGoTSRPCClient(url string) *DemoGoTSRPCClient {
return NewDemoGoTSRPCClient(url, "/service/demo")
}
func NewDemoGoTSRPCClient(url string, endpoint string) *DemoGoTSRPCClient {
return &DemoGoTSRPCClient{
URL: url,
EndPoint: endpoint,
}
}
func (c *DemoGoTSRPCClient) ExtractAddress(person *Person) (addr *Address, e *Err, clientErr error) {
args := []interface{}{person}
reply := []interface{}{&addr, &e}
clientErr = gotsrpc.CallClient(c.URL, c.EndPoint, "ExtractAddress", args, reply)
return
}
func (c *DemoGoTSRPCClient) GiveMeAScalar() (amount nested.Amount, wahr nested.True, hier ScalarInPlace, clientErr error) {
args := []interface{}{}
reply := []interface{}{&amount, &wahr, &hier}
clientErr = gotsrpc.CallClient(c.URL, c.EndPoint, "GiveMeAScalar", args, reply)
return
}
func (c *DemoGoTSRPCClient) Hello(name string) (reply string, err *Err, clientErr error) {
args := []interface{}{name}
reply := []interface{}{&reply, &err}
clientErr = gotsrpc.CallClient(c.URL, c.EndPoint, "Hello", args, reply)
return
}
func (c *DemoGoTSRPCClient) MapCrap() (crap map[string][]int, clientErr error) {
args := []interface{}{}
reply := []interface{}{&crap}
clientErr = gotsrpc.CallClient(c.URL, c.EndPoint, "MapCrap", args, reply)
return
}
func (c *DemoGoTSRPCClient) Nest() (retNest_0 *nested.Nested, clientErr error) {
args := []interface{}{}
reply := []interface{}{&retNest_0}
clientErr = gotsrpc.CallClient(c.URL, c.EndPoint, "Nest", args, reply)
return
}
func (c *DemoGoTSRPCClient) TestScalarInPlace() (retTestScalarInPlace_0 ScalarInPlace, clientErr error) {
args := []interface{}{}
reply := []interface{}{&retTestScalarInPlace_0}
clientErr = gotsrpc.CallClient(c.URL, c.EndPoint, "TestScalarInPlace", args, reply)
return
}

36
glide.lock generated Normal file
View File

@ -0,0 +1,36 @@
hash: ceb81b420e31dbc1d0fac128b688dfb563ab5fc6e0220d995dc20830e7a127c8
updated: 2016-12-17T07:08:03.465146982+01:00
imports:
- name: github.com/beorn7/perks
version: 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9
subpackages:
- quantile
- name: github.com/golang/protobuf
version: da116c3771bf4a398a43f44e069195ef1c9688ef
subpackages:
- proto
- name: github.com/matttproud/golang_protobuf_extensions
version: c12348ce28de40eed0136aa2b644d0ee0650e56c
subpackages:
- pbutil
- name: github.com/prometheus/client_golang
version: c5b7fccd204277076155f10851dad72b76a49317
subpackages:
- prometheus
- name: github.com/prometheus/client_model
version: fa8ad6fec33561be4280a8f0514318c79d7f6cb6
subpackages:
- go
- name: github.com/prometheus/common
version: 0d5de9d6d8629cb8bee6d4674da4127cd8b615a3
subpackages:
- expfmt
- internal/bitbucket.org/ww/goautoneg
- model
- name: github.com/prometheus/procfs
version: abf152e5f3e97f2fafac028d2cc06c1feb87ffa5
- name: github.com/valyala/gorpc
version: 908281bef77441f2d0ec1792189b4032a1dace0c
- name: gopkg.in/yaml.v2
version: a5b47d31c556af34a302ce5d659e6fea44d90de0
testImports: []

8
glide.yaml Normal file
View File

@ -0,0 +1,8 @@
package: github.com/foomo/gotsrpc
import:
- package: github.com/prometheus/client_golang
version: ~0.8.0
subpackages:
- prometheus
- package: github.com/valyala/gorpc
- package: gopkg.in/yaml.v2

504
go.go
View File

@ -3,11 +3,14 @@ package gotsrpc
import (
"fmt"
"strings"
"github.com/foomo/gotsrpc/config"
)
func (v *Value) isHTTPResponseWriter() bool {
return v.StructType != nil && v.StructType.Name == "ResponseWriter" && v.StructType.Package == "net/http"
}
func (v *Value) isHTTPRequest() bool {
return v.IsPtr && v.StructType != nil && v.StructType.Name == "Request" && v.StructType.Package == "net/http"
}
@ -26,10 +29,20 @@ func (v *Value) goType(aliases map[string]string, packageName string) (t string)
t += aliases[v.StructType.Package] + "."
}
t += v.StructType.Name
case v.Map != nil:
t += `map[` + v.Map.KeyType + `]` + v.Map.Value.goType(aliases, packageName)
case v.Scalar != nil:
// TODO this is a hack to retrieve string types
if packageName != v.Scalar.Package {
t += aliases[v.Scalar.Package] + "."
}
t += v.Scalar.Name[len(v.Scalar.Package)+1:]
default:
// TODO
fmt.Println("WARN: can't resolve goType")
}
return
return
}
func (v *Value) emptyLiteral(aliases map[string]string) (e string) {
@ -50,10 +63,24 @@ func (v *Value) emptyLiteral(aliases map[string]string) (e string) {
return "float64(0.0)"
case "int":
return "int(0)"
case "int8":
return "int8(0)"
case "int16":
return "int16(0)"
case "int32":
return "int32(0)"
case "int64":
return "int64(0)"
case "uint":
return "uint(0)"
case "uint8":
return "uint8(0)"
case "uint16":
return "uint16(0)"
case "uint32":
return "uint32(0)"
case "uint64":
return "uint64(0)"
case "bool":
return "false"
}
@ -102,43 +129,55 @@ func strfirst(str string, strfunc func(string) string) string {
}
return res
}
func renderServiceProxies(services map[string]*Service, fullPackageName string, packageName string, g *code) error {
aliases := map[string]string{
"net/http": "http",
"time": "time",
"github.com/foomo/gotsrpc": "gotsrpc",
}
r := strings.NewReplacer(".", "_", "/", "_", "-", "_")
extractImports := func(fields []*Field) {
for _, f := range fields {
if f.Value.StructType != nil {
st := f.Value.StructType
if st.Package != fullPackageName {
alias, ok := aliases[st.Package]
if !ok {
packageParts := strings.Split(st.Package, "/")
beautifulAlias := packageParts[len(packageParts)-1]
uglyAlias := r.Replace(st.Package)
alias = beautifulAlias
for _, otherAlias := range aliases {
if otherAlias == beautifulAlias {
alias = uglyAlias
break
}
}
aliases[st.Package] = alias
}
func extractImports(fields []*Field, fullPackageName string, aliases map[string]string) {
r := strings.NewReplacer(".", "_", "/", "_", "-", "_")
extractImport := func(st *StructType) {
if st.Package != fullPackageName {
alias, ok := aliases[st.Package]
if !ok {
packageParts := strings.Split(st.Package, "/")
beautifulAlias := packageParts[len(packageParts)-1]
uglyAlias := r.Replace(st.Package)
alias = beautifulAlias
for _, otherAlias := range aliases {
if otherAlias == beautifulAlias {
alias = uglyAlias
break
}
}
aliases[st.Package] = alias
}
}
}
for _, s := range services {
for _, m := range s.Methods {
extractImports(m.Args)
for _, f := range fields {
if f.Value.StructType != nil {
extractImport(f.Value.StructType)
} else if f.Value.Array != nil && f.Value.Array.Value.StructType != nil {
extractImport(f.Value.Array.Value.StructType)
}
}
}
func renderTSRPCServiceProxies(services map[string]*Service, fullPackageName string, packageName string, config *config.Target, g *code) error {
aliases := map[string]string{
"time": "time",
"net/http": "http",
"github.com/foomo/gotsrpc": "gotsrpc",
}
for _, service := range services {
// Check if we should render this service as ts rcp
// Note: remove once there's a separate gorcp generator
if !config.IsTSRPC(service.Name) {
continue
}
for _, m := range service.Methods {
extractImports(m.Args, fullPackageName, aliases)
}
}
@ -155,6 +194,12 @@ func renderServiceProxies(services map[string]*Service, fullPackageName string,
)
`)
for endpoint, service := range services {
// Check if we should render this service as ts rcp
// Note: remove once there's a separate gorcp generator
if !config.IsTSRPC(service.Name) {
continue
}
proxyName := service.Name + "GoTSRPCProxy"
g.l(`
type ` + proxyName + ` struct {
@ -179,7 +224,7 @@ func renderServiceProxies(services map[string]*Service, fullPackageName string,
service: service,
}
}
// ServeHTTP exposes your service
func (p *` + proxyName + `) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@ -244,6 +289,9 @@ func renderServiceProxies(services map[string]*Service, fullPackageName string,
args = append(args, arg.Value.emptyLiteral(aliases))
switch arg.Value.GoScalarType {
// case "int", "int8", "int16", "int32", "int64",
// "uint", "uint8", "uint16", "uint32", "uint64":
// callArgs = append(callArgs, fmt.Sprint(arg.Value.GoScalarType+"(args[", skipArgI, "].(float64))"))
default:
// assert
callArgs = append(callArgs, fmt.Sprint("args[", skipArgI, "].("+arg.Value.goType(aliases, fullPackageName)+")"))
@ -292,14 +340,398 @@ func renderServiceProxies(services map[string]*Service, fullPackageName string,
g.ind(1).l("http.Error(w, \"404 - not found \" + r.URL.Path, http.StatusNotFound)")
g.ind(-2).l("}") // close switch
g.ind(-1).l("}") // close ServeHttp
}
return nil
}
func RenderGo(services map[string]*Service, longPackageName, packageName string) (gocode string, err error) {
func renderTSRPCServiceClients(services map[string]*Service, fullPackageName string, packageName string, config *config.Target, g *code) error {
aliases := map[string]string{
"github.com/foomo/gotsrpc": "gotsrpc",
}
for _, service := range services {
// Check if we should render this service as ts rcp
// Note: remove once there's a separate gorcp generator
if !config.IsTSRPC(service.Name) {
continue
}
for _, m := range service.Methods {
extractImports(m.Args, fullPackageName, aliases)
extractImports(m.Return, fullPackageName, aliases)
}
}
imports := ""
for packageName, alias := range aliases {
imports += alias + " \"" + packageName + "\"\n"
}
g.l(`
// this file was auto generated by gotsrpc https://github.com/foomo/gotsrpc
package ` + packageName + `
import (
` + imports + `
)
`)
for endpoint, service := range services {
// Check if we should render this service as ts rcp
// Note: remove once there's a separate gorcp generator
if !config.IsTSRPC(service.Name) {
continue
}
clientName := service.Name + "GoTSRPCClient"
g.l(`
type ` + clientName + ` struct {
URL string
EndPoint string
}
func NewDefault` + clientName + `(url string) *` + clientName + ` {
return New` + clientName + `(url, "` + endpoint + `")
}
func New` + clientName + `(url string, endpoint string) *` + clientName + ` {
return &` + clientName + `{
URL: url,
EndPoint: endpoint,
}
}
`)
for _, method := range service.Methods {
args := []string{}
params := []string{}
for _, a := range method.Args {
args = append(args, a.Name)
params = append(params, a.Name+" "+a.Value.goType(aliases, fullPackageName))
}
rets := []string{}
returns := []string{}
for i, r := range method.Return {
name := r.Name
if len(name) == 0 {
name = fmt.Sprintf("ret%s_%d", method.Name, i)
}
rets = append(rets, "&"+name)
returns = append(returns, name+" "+r.Value.goType(aliases, fullPackageName))
}
returns = append(returns, "clientErr error")
g.l(`func (c *` + clientName + `) ` + method.Name + `(` + strings.Join(params, ", ") + `) (` + strings.Join(returns, ", ") + `) {`)
g.l(`args := []interface{}{` + strings.Join(args, ", ") + `}`)
g.l(`reply := []interface{}{` + strings.Join(rets, ", ") + `}`)
g.l(`clientErr = gotsrpc.CallClient(c.URL, c.EndPoint, "` + method.Name + `", args, reply)`)
g.l(`return`)
g.l(`}`)
g.nl()
}
}
return nil
}
func renderGoRPCServiceProxies(services map[string]*Service, fullPackageName string, packageName string, config *config.Target, g *code) error {
aliases := map[string]string{
"fmt": "fmt",
"time": "time",
"strings": "strings",
"reflect": "reflect",
"crypto/tls": "tls",
"encoding/gob": "gob",
"github.com/valyala/gorpc": "gorpc",
"github.com/foomo/gotsrpc": "gotsrpc",
}
for _, service := range services {
if !config.IsGoRPC(service.Name) {
continue
}
for _, m := range service.Methods {
extractImports(m.Args, fullPackageName, aliases)
extractImports(m.Return, fullPackageName, aliases)
}
}
imports := ""
for packageName, alias := range aliases {
imports += alias + " \"" + packageName + "\"\n"
}
g.l(`
// this file was auto generated by gotsrpc https://github.com/foomo/gotsrpc
package ` + packageName + `
import (
` + imports + `
)
`)
for _, service := range services {
if !config.IsGoRPC(service.Name) {
continue
}
proxyName := service.Name + "GoRPCProxy"
// Types
g.l(`type (`)
// Proxy type
g.l(`
` + proxyName + ` struct {
server *gorpc.Server
service *` + service.Name + `
callStatsHandler gotsrpc.GoRPCCallStatsHandlerFun
}
`)
// Request & Response types
for _, method := range service.Methods {
// Request type
g.l(ucfirst(service.Name+method.Name) + `Request struct {`)
for _, a := range method.Args {
g.l(ucfirst(a.Name) + ` ` + a.Value.goType(aliases, fullPackageName))
}
g.l(`}`)
// Response type
g.l(ucfirst(service.Name+method.Name) + `Response struct {`)
for i, r := range method.Return {
name := r.Name
if len(name) == 0 {
name = fmt.Sprintf("ret%s_%d", method.Name, i)
}
g.l(ucfirst(name) + ` ` + r.Value.goType(aliases, fullPackageName))
}
g.l(`}`)
g.nl()
}
g.l(`)`)
g.nl()
// Init
g.l(`func init() {`)
for _, method := range service.Methods {
g.l(`gob.Register(` + ucfirst(service.Name+method.Name) + `Request{})`)
g.l(`gob.Register(` + ucfirst(service.Name+method.Name) + `Response{})`)
}
g.l(`}`)
// Constructor
g.l(`
func New` + proxyName + `(addr string, service *` + service.Name + `, tlsConfig *tls.Config) *` + proxyName + ` {
proxy := &` + proxyName + `{
service: service,
}
if tlsConfig != nil {
proxy.server = gorpc.NewTLSServer(addr, proxy.handler, tlsConfig)
} else {
proxy.server = gorpc.NewTCPServer(addr, proxy.handler)
}
return proxy
}
func (p *` + proxyName + `) Start() error {
return p.server.Start()
}
func (p *` + proxyName + `) Serve() error {
return p.server.Serve()
}
func (p *` + proxyName + `) Stop() {
p.server.Stop()
}
func (p *` + proxyName + `) SetCallStatsHandler(handler gotsrpc.GoRPCCallStatsHandlerFun) {
p.callStatsHandler = handler
}
`)
g.nl()
// Handler
g.l(`func (p *` + proxyName + `) handler(clientAddr string, request interface{}) (response interface{}) {`)
g.l(`start := time.Now()`)
g.nl()
g.l(`reqType := reflect.TypeOf(request).String()`)
g.l(`funcNameParts := strings.Split(reqType, ".")`)
g.l(`funcName := funcNameParts[len(funcNameParts)-1]`)
g.nl()
g.l(`switch funcName {`)
for _, method := range service.Methods {
argParams := []string{}
for _, a := range method.Args {
argParams = append(argParams, "req."+ucfirst(a.Name))
}
rets := []string{}
retParams := []string{}
for i, r := range method.Return {
name := r.Name
if len(name) == 0 {
name = fmt.Sprintf("ret%s_%d", method.Name, i)
}
rets = append(rets, name)
retParams = append(retParams, ucfirst(name)+`: `+name)
}
g.l(`case "` + service.Name + method.Name + `Request":`)
if len(argParams) > 0 {
g.l(`req := request.(` + service.Name + method.Name + `Request)`)
}
if len(rets) > 0 {
g.l(strings.Join(rets, ", ") + ` := p.service.` + method.Name + `(` + strings.Join(argParams, ", ") + `)`)
} else {
g.l(`p.service.` + method.Name + `(` + strings.Join(argParams, ", ") + `)`)
}
g.l(`response = ` + service.Name + method.Name + `Response{` + strings.Join(retParams, ", ") + `}`)
}
g.l(`default:`)
g.l(`fmt.Println("Unkown request type", reflect.TypeOf(request).String())`)
g.l(`}`)
g.nl()
g.l(`if p.callStatsHandler != nil {`)
g.l(`p.callStatsHandler(&gotsrpc.CallStats{`)
g.l(`Func: funcName,`)
g.l(`Package: "` + fullPackageName + `",`)
g.l(`Service: "` + service.Name + `",`)
g.l(`Execution: time.Since(start),`)
g.l(`})`)
g.l(`}`)
g.nl()
g.l(`return`)
g.l(`}`)
}
return nil
}
func renderGoRPCServiceClients(services map[string]*Service, fullPackageName string, packageName string, config *config.Target, g *code) error {
aliases := map[string]string{
"crypto/tls": "tls",
"github.com/valyala/gorpc": "gorpc",
}
for _, service := range services {
if !config.IsGoRPC(service.Name) {
continue
}
for _, m := range service.Methods {
extractImports(m.Args, fullPackageName, aliases)
extractImports(m.Return, fullPackageName, aliases)
}
}
imports := ""
for packageName, alias := range aliases {
imports += alias + " \"" + packageName + "\"\n"
}
g.l(`
// this file was auto generated by gotsrpc https://github.com/foomo/gotsrpc
package ` + packageName + `
import (
` + imports + `
)
`)
for _, service := range services {
if !config.IsGoRPC(service.Name) {
continue
}
clientName := service.Name + "GoRPCClient"
// Client type
g.l(`
type ` + clientName + ` struct {
Client *gorpc.Client
}
`)
// Constructor
g.l(`
func New` + clientName + `(addr string, tlsConfig *tls.Config) *` + clientName + ` {
client := &` + clientName + `{}
if tlsConfig == nil {
client.Client = gorpc.NewTCPClient(addr)
} else {
client.Client = gorpc.NewTLSClient(addr, tlsConfig)
}
return client
}
func (c *` + clientName + `) Start() {
c.Client.Start()
}
func (c *` + clientName + `) Stop() {
c.Client.Stop()
}
`)
g.nl()
// Methods
for _, method := range service.Methods {
args := []string{}
params := []string{}
for _, a := range method.Args {
args = append(args, ucfirst(a.Name)+`: `+a.Name)
params = append(params, a.Name+" "+a.Value.goType(aliases, fullPackageName))
}
rets := []string{}
returns := []string{}
for i, r := range method.Return {
name := r.Name
if len(name) == 0 {
name = fmt.Sprintf("ret%s_%d", method.Name, i)
}
rets = append(rets, "response."+ucfirst(name))
returns = append(returns, name+" "+r.Value.goType(aliases, fullPackageName))
}
returns = append(returns, "clientErr error")
g.l(`func (c *` + clientName + `) ` + method.Name + `(` + strings.Join(params, ", ") + `) (` + strings.Join(returns, ", ") + `) {`)
g.l(`req := ` + service.Name + method.Name + `Request{` + strings.Join(args, ", ") + `}`)
if len(rets) > 0 {
g.l(`rpcCallRes, rpcCallErr := c.Client.Call(req)`)
} else {
g.l(`_, rpcCallErr := c.Client.Call(req)`)
}
g.l(`if rpcCallErr != nil {`)
g.l(`clientErr = rpcCallErr`)
g.l(`return`)
g.l(`}`)
if len(rets) > 0 {
g.l(`response := rpcCallRes.(` + service.Name + method.Name + `Response)`)
g.l(`return ` + strings.Join(rets, ", ") + `, nil`)
} else {
g.l(`return nil`)
}
g.l(`}`)
g.nl()
}
}
return nil
}
func RenderGoTSRPCProxies(services map[string]*Service, longPackageName, packageName string, config *config.Target) (gocode string, err error) {
g := newCode(" ")
err = renderServiceProxies(services, longPackageName, packageName, g)
err = renderTSRPCServiceProxies(services, longPackageName, packageName, config, g)
if err != nil {
return
}
gocode = g.string()
return
}
func RenderGoTSRPCClients(services map[string]*Service, longPackageName, packageName string, config *config.Target) (gocode string, err error) {
g := newCode(" ")
err = renderTSRPCServiceClients(services, longPackageName, packageName, config, g)
if err != nil {
return
}
gocode = g.string()
return
}
func RenderGoRPCProxies(services map[string]*Service, longPackageName, packageName string, config *config.Target) (gocode string, err error) {
g := newCode(" ")
err = renderGoRPCServiceProxies(services, longPackageName, packageName, config, g)
if err != nil {
return
}
gocode = g.string()
return
}
func RenderGoRPCClients(services map[string]*Service, longPackageName, packageName string, config *config.Target) (gocode string, err error) {
g := newCode(" ")
err = renderGoRPCServiceClients(services, longPackageName, packageName, config, g)
if err != nil {
return
}

4
gorpc.go Normal file
View File

@ -0,0 +1,4 @@
package gotsrpc
type GoRPCCallStatsHandlerFun func(stats *CallStats)

View File

@ -8,6 +8,7 @@ import (
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
"net/http"
"path"
"strings"
@ -37,13 +38,17 @@ func ErrorMethodNotAllowed(w http.ResponseWriter) {
func LoadArgs(args interface{}, callStats *CallStats, r *http.Request) error {
start := time.Now()
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&args)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
if err := json.Unmarshal(body, &args); err != nil {
return err
}
if callStats != nil {
callStats.Unmarshalling = time.Now().Sub(start)
callStats.RequestSize = len(body)
}
return nil
}
@ -71,6 +76,7 @@ func Reply(response []interface{}, stats *CallStats, r *http.Request, w http.Res
return
}
if stats != nil {
stats.ResponseSize = len(jsonBytes)
stats.Marshalling = time.Now().Sub(serializationStart)
}
//r = r.WithContext(ctx)
@ -88,12 +94,24 @@ func jsonDump(v interface{}) {
fmt.Println(string(jsonBytes))
}
func parsePackage(goPath string, packageName string) (pkg *ast.Package, err error) {
fset := token.NewFileSet()
dir := path.Join(goPath, "src", packageName)
pkgs, err := parser.ParseDir(fset, dir, nil, parser.AllErrors)
func parseDir(goPaths []string, packageName string) (map[string]*ast.Package, error) {
errorStrings := map[string]string{}
for _, goPath := range goPaths {
fset := token.NewFileSet()
dir := path.Join(goPath, "src", packageName)
pkgs, err := parser.ParseDir(fset, dir, nil, parser.AllErrors)
if err == nil {
return pkgs, nil
}
errorStrings[dir] = err.Error()
}
return nil, errors.New("could not parse dir for package name: " + packageName + " in goPaths " + strings.Join(goPaths, ", ") + " : " + fmt.Sprint(errorStrings))
}
func parsePackage(goPaths []string, packageName string) (pkg *ast.Package, err error) {
pkgs, err := parseDir(goPaths, packageName)
if err != nil {
return nil, err
return nil, errors.New("could not parse package " + packageName + ": " + err.Error())
}
packageNameParts := strings.Split(packageName, "/")
if len(packageNameParts) == 0 {

107
php.go Normal file
View File

@ -0,0 +1,107 @@
package gotsrpc
import (
"github.com/foomo/gotsrpc/config"
"strings"
)
func renderPHPRPCServiceClients(service *Service, namespce string, g *code) error {
g.l(`<?php`)
g.l(`// this file was auto generated by gotsrpc https://github.com/foomo/gotsrpc`)
g.nl()
g.l(`namespace ` + namespce + `;`)
g.nl()
g.l(`class ` + service.Name + `Client`)
g.l(`{`)
g.ind(1)
// Variables
g.l(`public static $defaultOptions = ['http' => ['method' => 'POST', 'timeout' => 5, 'header' => 'Content-type: application/json']];`)
g.nl()
g.l(`private $options;`)
g.l(`private $endpoint;`)
g.nl()
// Constructor
g.l(`/**`)
g.l(` * @param string $endpoint`)
g.l(` * @param array $options`)
g.l(` */`)
g.l(`public function __construct($endpoint, array $options=null)`)
g.l(`{`)
g.ind(1)
g.l(`$this->endpoint = $endpoint;`)
g.l(`$this->options = (is_null($options)) ? self::$defaultOptions : $options;`)
g.ind(-1)
g.l(`}`)
g.nl()
// Service methods
for _, method := range service.Methods {
args := []string{}
params := []string{}
g.l(`/**`)
for _, a := range method.Args {
args = append(args, "$"+a.Name)
params = append(params, "$"+a.Name)
g.l(` * @param $` + a.Name)
}
g.l(` * @return array`)
g.l(` */`)
g.l(`public function ` + lcfirst(method.Name) + `(` + strings.Join(params, ", ") + `)`)
g.l(`{`)
g.ind(1)
g.l(`return $this->call('` + method.Name + `', [` + strings.Join(params, ", ") + `]);`)
g.ind(-1)
g.l(`}`)
g.nl()
}
// Protected methods
g.l(`/**`)
g.l(` * @param string $method`)
g.l(` * @param array $request`)
g.l(` * @return array`)
g.l(` * @throws \Exception`)
g.l(` */`)
g.l(`protected function call($method, array $request)`)
g.l(`{`)
g.ind(1)
g.l(`$options = $this->options;`)
g.l(`$options['http']['content'] = json_encode($request);`)
g.l(`if (false === $content = @file_get_contents($this->endpoint . '/' . $method, false, stream_context_create($options))) {`)
g.ind(1)
g.l(`$err = error_get_last();`)
g.l(`throw new \Exception($err['message'], $err['type']);`)
g.ind(-1)
g.l(`}`)
g.l(`return json_decode($content);`)
g.ind(-1)
g.l(`}`)
g.nl()
g.ind(-1)
g.l(`}`)
g.nl()
return nil
}
func RenderPHPRPCClients(services map[string]*Service, config *config.Target) (code map[string]string, err error) {
code = map[string]string{}
for _, service := range services {
// Check if we should render this service as ts rcp
// Note: remove once there's a separate gorcp generator
if !config.IsPHPRPC(service.Name) {
continue
}
target := config.GetPHPTarget(service.Name)
g := newCode(" ")
if err = renderPHPRPCServiceClients(service, target.Namespace, g); err != nil {
return
}
code[target.Out+"/"+service.Name+"Client.php"] = g.string()
}
return
}

View File

@ -8,22 +8,46 @@ import (
)
func InstrumentService(s http.HandlerFunc) (handler http.HandlerFunc) {
counterVec := p.NewSummaryVec(p.SummaryOpts{
requestDuration := p.NewSummaryVec(p.SummaryOpts{
Namespace: "gotsrpc",
Subsystem: "service",
Name: "time_nanoseconds",
Help: "nanoseconds to unmarshal requests, execute a service function and marshal its reponses",
}, []string{"package", "service", "func", "type"})
p.MustRegister(counterVec)
requestSize := p.NewSummaryVec(p.SummaryOpts{
Namespace: "gotsrpc",
Subsystem: "service",
Name: "size_bytes",
Help: "request and response sizes in bytes",
}, []string{"package", "service", "func", "type"})
p.MustRegister(requestSize)
p.MustRegister(requestDuration)
return func(w http.ResponseWriter, r *http.Request) {
*r = *gotsrpc.RequestWithStatsContext(r)
s.ServeHTTP(w, r)
stats := gotsrpc.GetStatsForRequest(r)
if stats != nil {
counterVec.WithLabelValues(stats.Package, stats.Service, stats.Func, "unmarshalling").Observe(float64(stats.Unmarshalling))
counterVec.WithLabelValues(stats.Package, stats.Service, stats.Func, "execution").Observe(float64(stats.Execution))
counterVec.WithLabelValues(stats.Package, stats.Service, stats.Func, "marshalling").Observe(float64(stats.Marshalling))
requestSize.WithLabelValues(stats.Package, stats.Service, stats.Func, "request").Observe(float64(stats.RequestSize))
requestSize.WithLabelValues(stats.Package, stats.Service, stats.Func, "response").Observe(float64(stats.ResponseSize))
requestDuration.WithLabelValues(stats.Package, stats.Service, stats.Func, "unmarshalling").Observe(float64(stats.Unmarshalling))
requestDuration.WithLabelValues(stats.Package, stats.Service, stats.Func, "execution").Observe(float64(stats.Execution))
requestDuration.WithLabelValues(stats.Package, stats.Service, stats.Func, "marshalling").Observe(float64(stats.Marshalling))
}
}
}
func InstrumentGoRPCService() gotsrpc.GoRPCCallStatsHandlerFun {
callsCounter := p.NewSummaryVec(p.SummaryOpts{
Namespace: "gorpc",
Subsystem: "service",
Name: "time_seconds",
Help: "seconds to execute a service method",
}, []string{"package", "service", "func", "type"})
p.MustRegister(callsCounter)
return func(stats *gotsrpc.CallStats) {
callsCounter.WithLabelValues(stats.Package, stats.Service, stats.Func, "execution").Observe(float64(stats.Execution))
}
}

View File

@ -6,7 +6,6 @@ import (
"go/ast"
"go/token"
"reflect"
"runtime"
"sort"
"strings"
)
@ -182,13 +181,14 @@ func loadConstants(pkg *ast.Package) map[string]*ast.BasicLit {
}
func Read(goPath string, packageName string, serviceMap map[string]string) (services map[string]*Service, structs map[string]*Struct, scalars map[string]*Scalar, constants map[string]map[string]*ast.BasicLit, err error) {
func Read(goPaths []string, packageName string, serviceMap map[string]string) (services map[string]*Service, structs map[string]*Struct, scalars map[string]*Scalar, constants map[string]map[string]*ast.BasicLit, err error) {
if len(serviceMap) == 0 {
err = errors.New("nothing to do service names are empty")
return
}
pkg, err := parsePackage(goPath, packageName)
if err != nil {
pkg, parseErr := parsePackage(goPaths, packageName)
if parseErr != nil {
err = parseErr
return
}
services, err = readServicesInPackage(pkg, packageName, serviceMap)
@ -211,7 +211,7 @@ func Read(goPath string, packageName string, serviceMap map[string]string) (serv
structs = map[string]*Struct{}
scalars = map[string]*Scalar{}
collectErr := collectTypes(goPath, missingTypes, structs, scalars)
collectErr := collectTypes(goPaths, missingTypes, structs, scalars)
if collectErr != nil {
err = errors.New("error while collecting structs: " + collectErr.Error())
}
@ -225,7 +225,7 @@ func Read(goPath string, packageName string, serviceMap map[string]string) (serv
structPackage := structDef.Package
_, ok := constants[structPackage]
if !ok {
pkg, constPkgErr := parsePackage(goPath, structPackage)
pkg, constPkgErr := parsePackage(goPaths, structPackage)
if constPkgErr != nil {
err = constPkgErr
return
@ -265,7 +265,7 @@ func fixFieldStructs(fields []*Field, structs map[string]*Struct, scalars map[st
}
}
func collectTypes(goPath string, missingTypes map[string]bool, structs map[string]*Struct, scalars map[string]*Scalar) error {
func collectTypes(goPaths []string, missingTypes map[string]bool, structs map[string]*Struct, scalars map[string]*Scalar) error {
scannedPackageStructs := map[string]map[string]*Struct{}
scannedPackageScalars := map[string]map[string]*Scalar{}
missingTypeNames := func() []string {
@ -297,19 +297,19 @@ func collectTypes(goPath string, missingTypes map[string]bool, structs map[strin
packageStructs, structOK := scannedPackageStructs[packageName]
packageScalars, scalarOK := scannedPackageScalars[packageName]
if !structOK || !scalarOK {
parsedPackageStructs, parsedPackageScalars, err := getTypesInPackage(goPath, packageName)
parsedPackageStructs, parsedPackageScalars, err := getTypesInPackage(goPaths, packageName)
if err != nil {
return err
}
trace("found structs in", goPath, packageName)
trace("found structs in", goPaths, packageName)
for structName, strct := range packageStructs {
trace(" struct", structName, strct)
if strct == nil {
panic("how could that be")
}
}
trace("found scalars in", goPath, packageName)
trace("found scalars in", goPaths, packageName)
for scalarName, scalar := range parsedPackageScalars {
trace(" scalar", scalarName, scalar)
}
@ -417,13 +417,10 @@ func (st *StructType) FullName() string {
return fullName
}
func getTypesInPackage(goPath string, packageName string) (structs map[string]*Struct, scalars map[string]*Scalar, err error) {
pkg, err := parsePackage(goPath, packageName)
func getTypesInPackage(goPaths []string, packageName string) (structs map[string]*Struct, scalars map[string]*Scalar, err error) {
pkg, err := parsePackage(goPaths, packageName)
if err != nil {
pkg, err = parsePackage(runtime.GOROOT(), packageName)
if err != nil {
return nil, nil, err
}
return nil, nil, err
}
structs, scalars, err = readStructs(pkg, packageName)
if err != nil {

View File

@ -7,4 +7,6 @@ type CallStats struct {
Execution time.Duration
Marshalling time.Duration
Unmarshalling time.Duration
RequestSize int
ResponseSize int
}

View File

@ -92,7 +92,9 @@ func getScalarFromAstIdent(ident *ast.Ident) ScalarType {
return ScalarTypeString
case "bool":
return ScalarTypeBool
case "int", "int32", "int64", "float", "float32", "float64":
case "float", "float32", "float64",
"int", "int8", "int16", "int32", "int64",
"uint", "uint8", "uint16", "uint32", "uint64":
return ScalarTypeNumber
default:
if ident.Obj != nil && ident.Obj.Decl != nil && reflect.ValueOf(ident.Obj.Decl).Type().String() == "*ast.TypeSpec" {
@ -202,25 +204,27 @@ func (v *Value) loadExpr(expr ast.Expr, fileImports fileImportSpecMap) {
v.Array = &Array{Value: &Value{}}
switch reflect.ValueOf(fieldArray.Elt).Type().String() {
case "*ast.ArrayType":
//readAstArrayType(v.Array.Value, fieldArray.Elt.(*ast.ArrayType), fileImports)
v.Array.Value.loadExpr(fieldArray.Elt.(*ast.ArrayType), fileImports)
case "*ast.Ident":
readAstType(v.Array.Value, fieldArray.Elt.(*ast.Ident), fileImports)
case "*ast.StarExpr":
readAstStarExpr(v.Array.Value, fieldArray.Elt.(*ast.StarExpr), fileImports)
case "*ast.ArrayType":
//readAstArrayType(v.Array.Value, fieldArray.Elt.(*ast.ArrayType), fileImports)
v.Array.Value.loadExpr(fieldArray.Elt.(*ast.ArrayType), fileImports)
case "*ast.MapType":
v.Array.Value.Map = &Map{
Value: &Value{},
}
readAstMapType(v.Array.Value.Map, fieldArray.Elt.(*ast.MapType), fileImports)
case "*ast.SelectorExpr":
readAstSelectorExpr(v.Array.Value, fieldArray.Elt.(*ast.SelectorExpr), fileImports)
case "*ast.StructType":
readAstStructType(v.Array.Value, fieldArray.Elt.(*ast.StructType), fileImports)
default:
trace("---------------------> array of", reflect.ValueOf(fieldArray.Elt).Type().String())
}
case "*ast.Ident":
fieldIdent := expr.(*ast.Ident)
readAstType(v, fieldIdent, fileImports)
case "*ast.StarExpr":
// a pointer on sth

View File

@ -285,7 +285,7 @@ func ucFirst(str string) string {
return constPrefix
}
func RenderTypeScriptServices(moduleKind config.ModuleKind, services map[string]*Service, mappings config.TypeScriptMappings, scalarTypes map[string]*Scalar, tsModuleName string) (typeScript string, err error) {
func RenderTypeScriptServices(moduleKind config.ModuleKind, services map[string]*Service, mappings config.TypeScriptMappings, scalarTypes map[string]*Scalar, target *config.Target) (typeScript string, err error) {
ts := newCode(" ")
if !SkipGoTSRPC {
@ -299,7 +299,7 @@ func RenderTypeScriptServices(moduleKind config.ModuleKind, services map[string]
request.open('POST', endPoint + "/" + encodeURIComponent(method), true);
// this causes problems, when the browser decides to do a cors OPTIONS request
// request.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
request.send(JSON.stringify(args));
request.send(JSON.stringify(args));
request.onload = function() {
if (request.status == 200) {
try {
@ -311,7 +311,7 @@ func RenderTypeScriptServices(moduleKind config.ModuleKind, services map[string]
} else {
err(request);
}
};
};
request.onerror = function() {
err(request);
};
@ -323,11 +323,16 @@ func RenderTypeScriptServices(moduleKind config.ModuleKind, services map[string]
if !SkipGoTSRPC {
ts.l("} // close")
}
ts.l("module " + tsModuleName + " {")
ts.l("module " + target.TypeScriptModule + " {")
ts.ind(1)
}
for endPoint, service := range services {
// Check if we should render this service as ts rcp
// Note: remove once there's a separate gorcp generator
if !target.IsTSRPC(service.Name) {
continue
}
err = renderService(SkipGoTSRPC, moduleKind, service, endPoint, mappings, scalarTypes, ts)
if err != nil {
return