diff --git a/ae_install.sh b/ae_install.sh index 24d6124..d3364d2 100755 --- a/ae_install.sh +++ b/ae_install.sh @@ -11,5 +11,3 @@ for DIR in "webrpc"; do mkdir -p "${ROOT}/${PREFIX}/${DIR}" cp -R "${RPCGEN}/${DIR}/"* "${ROOT}/${PREFIX}/${DIR}/" done - -rm "${ROOT}/${PREFIX}/webrpc/proto.go" diff --git a/ae_protoc.sh b/ae_protoc.sh index 555f3ca..18f4a41 100755 --- a/ae_protoc.sh +++ b/ae_protoc.sh @@ -2,12 +2,23 @@ set -e +# This will make the pb only compile for appengine +AE_COPY=1 +if [[ "$1" == "--ae-only" ]]; then + AE_COPY=0 +fi + if [[ "$#" -eq 0 ]]; then - echo "Usage: ae_protoc.sh " + echo "Usage: ae_protoc.sh [--ae-only] [ ...]" echo " This script will use the protoc in your path" echo "to compile each proto into go source and will" echo "edit each file to be usable on AppEngine by" echo "removing references to the goprotobuf library." + echo + echo "Unless --ae-also is specified, the compiled protobuf" + echo "output file will be duplicated to an appengine" + echo "specific .ae.go and both will be guarded with the" + echo "appropriate +build directive." exit 1 fi @@ -19,13 +30,48 @@ for FILE in "$@"; do echo "Compiling $FILE..." protoc --go_out=. "$FILE" + # Determine file names PB_FILE="${FILE%.proto}.pb.go" - echo "Sanitizing $PB_FILE..." + if [[ $AE_COPY -ne 0 ]]; then + AE_FILE="${FILE%.proto}.ae.go" + cp "$PB_FILE" "$AE_FILE" + else + AE_FILE="$PB_FILE" + fi + + echo "Sanitizing $AE_FILE..." { - echo "H" # Display human-readable errors, if any - echo "g/goprotobuf/d" # Delete lines containing goprotobuf - echo "g/proto\./d" # Delete lines calling into the library - echo "w" # Write - echo "q" # Quit + echo "H" # Display human-readable errors, if any + echo "g/goprotobuf/d" # Delete lines containing goprotobuf + echo "g/proto\./d" # Delete lines calling into the library + echo "w" # Write + echo "q" # Quit } | ed -s "$PB_FILE" + + if [[ $AE_COPY -eq 0 ]]; then + # If we are sharing the same proto, don't insert guards + continue + fi + + echo "Guarding $PB_FILE..." + { + echo "H" # Display human-readable errors, if any + echo "1i" # Insert at the beginning of the file + echo "// +build !appengine" # Don't compile this file under appengine + echo # Blank line to not confuse anything + echo "." # Exit insert mode + echo "w" # Write + echo "q" # Quit + } | ed -s "$PB_FILE" + + echo "Guarding $AE_FILE..." + { + echo "H" # Display human-readable errors, if any + echo "1i" # Insert at the beginning of the file + echo "// +build appengine" # Only compile this file under appengine + echo # Blank line to not confuse anything + echo "." # Exit insert mode + echo "w" # Write + echo "q" # Quit + } | ed -s "$AE_FILE" done diff --git a/example_ae/README.md b/example_ae/README.md index 2104ddd..31ab3a7 100644 --- a/example_ae/README.md +++ b/example_ae/README.md @@ -13,27 +13,53 @@ When deploying to AppEngine, you need two things: 1. Sanitized .pb.go files I have provided scripts in the root of the repository to help out with these. -The `ae_install.sh` script should be run from your appengine project's root directory -(the one containing `app.yaml`) and will copy the webrpc package into the proper place. -This will also automatically delete the local copy of `proto.go`, -which removes the protobuf support and the dependency upon it. -The `ae_protoc.go` script (which relies on `protoc-gen-go` being in your `PATH`) -will compile (for Go only) all of the `.proto` files specified on the command-line -with support for web services only (to avoid the dependency on the codec package) -and will sanitize the generated file to remove references to goprotobuf. -A side-effect of the sanitization is that there will no longer be a `.String()` -method on the generated objects; you may add one manually if you wish, -but I would recommend doing it in a parallel `.go` file so that regeneration won't kill it. + +Getting `go-rpcgen/webrpc` in your app +-------------------------------------- + +The `ae_install.sh` script should be run from your appengine project's root +directory (the one containing `app.yaml`) and will copy the webrpc package into +the proper place. The webrpc package contains proto support, but the go1 +version of appengine will ignore this file. + +Getting sanitized protobufs in your app +--------------------------------------- + +The `ae_protoc.go` script (which relies on `protoc-gen-go` being in your +`PATH`) will compile (for Go only) all of the `.proto` files specified on the +command-line. + +The script has been changed since its previous version to generate both a full +`.pb.go` for use in normal applications and a `.ae.go` for use in appengine. +They both have compilation guards which cause them to be executed in the proper +context. + +Both proto files are compiled with support for web services only (to avoid the +dependency on the codec package) and the script will sanitize the duplicate +`.ae.go` file to remove references to goprotobuf. A side-effect of the +sanitization is that there will no longer be a `.String()` method on the +generated objects under appengine; you may add one manually if you wish, but I +would recommend doing it in an adjacent `.go` file (with the proper `+build` +guard for appengine) so that regeneration won't kill it. Testing This Example --------------------- +==================== + +To test this out, you will probably need `go1` installed locally so that you +can compile non-appengine binaries. The appengine `go` wrapper script in +particular doesn't like arguments to `go run` though it may be possible to use. + +Here are the basic steps: + - Update app.yaml with your application name and the latest Go SDK Version -- Execute the following to run locally: +- Execute the following (in the `example_ae` directory) to run locally: ../ae_install.sh ../ae_protoc.sh whoami/*.proto - (cd github.com/kylelemons/go-rpcgen/; mkdir -p ae_example; ln -s ../../../../whoami ae_example/) dev_appserver.py . + +- Then, in another shell (since `dev_appserver` blocks): + go run client/client.go http://localhost:6060/ - Run the following to test remotely: diff --git a/example_ae/app.yaml b/example_ae/app.yaml index e87ea9b..cdba491 100644 --- a/example_ae/app.yaml +++ b/example_ae/app.yaml @@ -1,7 +1,7 @@ application: your-application-name version: 1 runtime: go -api_version: go1beta +api_version: go1 handlers: - url: /.* diff --git a/example_ae/app/server.go b/example_ae/app/server.go index 0b55824..270863b 100644 --- a/example_ae/app/server.go +++ b/example_ae/app/server.go @@ -1,9 +1,12 @@ +// +build appengine + package server import ( "net/http" - _ "github.com/kylelemons/go-rpcgen/webrpc" "whoami" + + _ "github.com/kylelemons/go-rpcgen/webrpc" ) type server struct{} diff --git a/example_ae/client/.gitignore b/example_ae/client/.gitignore new file mode 100644 index 0000000..b051c6c --- /dev/null +++ b/example_ae/client/.gitignore @@ -0,0 +1 @@ +client diff --git a/example_ae/client/client.go b/example_ae/client/client.go index a63f49a..af06a2c 100644 --- a/example_ae/client/client.go +++ b/example_ae/client/client.go @@ -1,10 +1,12 @@ +// +build !appengine + package main import ( "os" "log" "net/url" - "github.com/kylelemons/go-rpcgen/ae_example/whoami" + "github.com/kylelemons/go-rpcgen/example_ae/whoami" "github.com/kylelemons/go-rpcgen/webrpc" ) diff --git a/example_ae/whoami/whoami.ae.go b/example_ae/whoami/whoami.ae.go new file mode 100644 index 0000000..335f2d1 --- /dev/null +++ b/example_ae/whoami/whoami.ae.go @@ -0,0 +1,84 @@ +// +build appengine + +// Code generated by protoc-gen-go from "whoami/whoami.proto" +// DO NOT EDIT! + +package whoami + +import proto "code.google.com/p/goprotobuf/proto" +import "math" + +import "net/url" +import "net/http" +import "github.com/kylelemons/go-rpcgen/webrpc" + +// Reference proto and math imports to suppress error if they are not otherwise used. +var _ = proto.GetString +var _ = math.Inf + +type Empty struct { + XXX_unrecognized []byte `json:",omitempty"` +} + +func (this *Empty) Reset() { *this = Empty{} } +func (this *Empty) String() string { return proto.CompactTextString(this) } + +type YouAre struct { + IpAddr *string `protobuf:"bytes,1,req,name=ip_addr" json:"ip_addr,omitempty"` + XXX_unrecognized []byte `json:",omitempty"` +} + +func (this *YouAre) Reset() { *this = YouAre{} } +func (this *YouAre) String() string { return proto.CompactTextString(this) } + +func init() { +} + +// WhoamiService is an interface satisfied by the generated client and +// which must be implemented by the object wrapped by the server. +type WhoamiService interface { + Whoami(in *Empty, out *YouAre) error +} + +// WhoamiServiceWeb is the web-based RPC version of the interface which +// must be implemented by the object wrapped by the webrpc server. +type WhoamiServiceWeb interface { + Whoami(r *http.Request, in *Empty, out *YouAre) error +} + +// internal wrapper for type-safe webrpc calling +type rpcWhoamiServiceWebClient struct { + remote *url.URL + protocol webrpc.Protocol +} + +func (this rpcWhoamiServiceWebClient) Whoami(in *Empty, out *YouAre) error { + return webrpc.Post(this.protocol, this.remote, "/WhoamiService/Whoami", in, out) +} + +// Register a WhoamiServiceWeb implementation with the given webrpc ServeMux. +// If mux is nil, the default webrpc.ServeMux is used. +func RegisterWhoamiServiceWeb(this WhoamiServiceWeb, mux webrpc.ServeMux) error { + if mux == nil { + mux = webrpc.DefaultServeMux + } + if err := mux.Handle("/WhoamiService/Whoami", func(c *webrpc.Call) error { + in, out := new(Empty), new(YouAre) + if err := c.ReadRequest(in); err != nil { + return err + } + if err := this.Whoami(c.Request, in, out); err != nil { + return err + } + return c.WriteResponse(out) + }); err != nil { + return err + } + return nil +} + +// NewWhoamiServiceWebClient returns a webrpc wrapper for calling the methods of WhoamiService +// remotely via the web. The remote URL is the base URL of the webrpc server. +func NewWhoamiServiceWebClient(pro webrpc.Protocol, remote *url.URL) WhoamiService { + return rpcWhoamiServiceWebClient{remote, pro} +} diff --git a/example_ae/whoami/whoami.pb.go b/example_ae/whoami/whoami.pb.go index 1fd34d7..c6f479c 100644 --- a/example_ae/whoami/whoami.pb.go +++ b/example_ae/whoami/whoami.pb.go @@ -1,20 +1,17 @@ -// Code generated by protoc-gen-go from "example_ae/whoami/whoami.proto" +// +build !appengine + +// Code generated by protoc-gen-go from "whoami/whoami.proto" // DO NOT EDIT! package whoami -import proto "code.google.com/p/goprotobuf/proto" import "math" -import "net" -import "net/rpc" -import "github.com/kylelemons/go-rpcgen/codec" import "net/url" import "net/http" import "github.com/kylelemons/go-rpcgen/webrpc" // Reference proto and math imports to suppress error if they are not otherwise used. -var _ = proto.GetString var _ = math.Inf type Empty struct { @@ -22,7 +19,6 @@ type Empty struct { } func (this *Empty) Reset() { *this = Empty{} } -func (this *Empty) String() string { return proto.CompactTextString(this) } type YouAre struct { IpAddr *string `protobuf:"bytes,1,req,name=ip_addr" json:"ip_addr,omitempty"` @@ -30,7 +26,6 @@ type YouAre struct { } func (this *YouAre) Reset() { *this = YouAre{} } -func (this *YouAre) String() string { return proto.CompactTextString(this) } func init() { } @@ -41,61 +36,6 @@ type WhoamiService interface { Whoami(in *Empty, out *YouAre) error } -// internal wrapper for type-safe RPC calling -type rpcWhoamiServiceClient struct { - *rpc.Client -} - -func (this rpcWhoamiServiceClient) Whoami(in *Empty, out *YouAre) error { - return this.Call("WhoamiService.Whoami", in, out) -} - -// NewWhoamiServiceClient returns an *rpc.Client wrapper for calling the methods of -// WhoamiService remotely. -func NewWhoamiServiceClient(conn net.Conn) WhoamiService { - return rpcWhoamiServiceClient{rpc.NewClientWithCodec(codec.NewClientCodec(conn))} -} - -// ServeWhoamiService serves the given WhoamiService backend implementation on conn. -func ServeWhoamiService(conn net.Conn, backend WhoamiService) error { - srv := rpc.NewServer() - if err := srv.RegisterName("WhoamiService", backend); err != nil { - return err - } - srv.ServeCodec(codec.NewServerCodec(conn)) - return nil -} - -// DialWhoamiService returns a WhoamiService for calling the WhoamiService servince at addr (TCP). -func DialWhoamiService(addr string) (WhoamiService, error) { - conn, err := net.Dial("tcp", addr) - if err != nil { - return nil, err - } - return NewWhoamiServiceClient(conn), nil -} - -// ListenAndServeWhoamiService serves the given WhoamiService backend implementation -// on all connections accepted as a result of listening on addr (TCP). -func ListenAndServeWhoamiService(addr string, backend WhoamiService) error { - clients, err := net.Listen("tcp", addr) - if err != nil { - return err - } - srv := rpc.NewServer() - if err := srv.RegisterName("WhoamiService", backend); err != nil { - return err - } - for { - conn, err := clients.Accept() - if err != nil { - return err - } - go srv.ServeCodec(codec.NewServerCodec(conn)) - } - panic("unreachable") -} - // WhoamiServiceWeb is the web-based RPC version of the interface which // must be implemented by the object wrapped by the webrpc server. type WhoamiServiceWeb interface {