Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(785)

Unified Diff: go/src/infra/gae/epservice/epservice.go

Issue 1153473008: A client/server helper wrapper for endpoints in Go. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: added comment Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « go/src/infra/gae/epclient/epclient.go ('k') | go/src/infra/gae/epservice/example/model.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: go/src/infra/gae/epservice/epservice.go
diff --git a/go/src/infra/gae/epservice/epservice.go b/go/src/infra/gae/epservice/epservice.go
new file mode 100644
index 0000000000000000000000000000000000000000..582fc44dd9103adc1dad33201b16425aacd0879a
--- /dev/null
+++ b/go/src/infra/gae/epservice/epservice.go
@@ -0,0 +1,250 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// EPService will run a local appengine service with all of the endpoint
+// services under this directory. The tool assumes that:
+// * goapp exists in PATH (and understands all the import paths in the
+// defined services)
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "os/signal"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "sync"
+ "text/template"
+ "time"
+
+ "infra/libs/jsutil"
+)
+
+var (
+ clearDatastore = flag.Bool("clear_datastore", false, "if set, clear the datastore.")
+ leak = flag.Bool("leak", false, "if set, leak the temporary directory.")
+ verbose = flag.Bool("verbose", false, "if set, print skipped packages.")
+ servicePackagesBase = flag.String("base", "infra",
+ "base Go package to walk to find service definitions.")
+)
+
+var serviceScript = `{{define "go"}}
+// DO NOT EDIT
+// Auto-generated by infra/gae/epservice
+// {{.Timestamp}}
+
+package main
+
+import (
+ "fmt"
+
+{{range $idx, $pkg := .Pkgs}}
+ pkg{{$idx}} "{{$pkg.Imp}}"{{end}}
+
+ "github.com/GoogleCloudPlatform/go-endpoints/endpoints"
+)
+
+func init() {
+ var err error
+ server := endpoints.NewServer("")
+
+ {{range $idx, $pkg := .Pkgs }}
+ err = pkg{{$idx}}.RegisterEndpointsService(server)
+ if err != nil {
+ panic(fmt.Errorf("Error while registering service {{$pkg}}: %s", err))
+ }
+ {{end}}
+
+ server.HandleHTTP(nil)
+}
+{{end}}
+`
+
+const appYaml = `{{define "yaml"}}
+# DO NOT EDIT
+# Auto-generated by infra/gae/epservice
+# {{.Timestamp}}
+
+application: epclient-tmp-app
+version: nope
+runtime: go
+api_version: go1
+
+handlers:
+- url: /.*
+ script: _go_app
+{{end}}
+`
+
+var templ = template.New("service")
+
+func init() {
+ template.Must(templ.Parse(serviceScript))
+ template.Must(templ.Parse(appYaml))
+}
+
+type templInput struct {
+ Pkgs []Pkg
+ Timestamp time.Time
+}
+
+// Pkg holds the import and real filesystem paths of an endpoint service
+// package. It's exported merely for reflection purposes, since it's used
+// by text/template.
+type Pkg struct {
+ Imp string
+ Pth string
+}
+
+func boom(err error) {
+ if err != nil {
+ panic(err)
+ }
+}
+
+// NOTE: if you format your RegisterEndpointsService implementation like a bozo,
+// then this won't match. Don't do that.
+var reRegisterEndpointsService = regexp.MustCompile(
+ `\nfunc RegisterEndpointsService\(\w* \*\w*\.Server\) error {\n`)
+
+func getPkgs() []Pkg {
+ cmd := exec.Command("goapp", "list", "-json", filepath.Join(*servicePackagesBase+"..."))
+ d, err := cmd.Output()
+ boom(err)
+
+ d = []byte("[" + strings.Replace(string(d), "}\n{", "},{", -1) + "]")
+
+ js := interface{}(nil)
+ boom(json.Unmarshal(d, &js))
+
+ ret := []Pkg{}
+
+ type pkgOk struct {
+ p Pkg
+ ok bool
+ }
+ pkgs := make(chan pkgOk)
+ wg := sync.WaitGroup{}
+
+ for _, m := range js.([]interface{}) {
+ pkg := Pkg{
+ jsutil.Get(m, "ImportPath").(string),
+ jsutil.Get(m, "Dir").(string),
+ }
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ paths, err := ioutil.ReadDir(pkg.Pth)
+ boom(err)
+
+ for _, f := range paths {
+ if !f.IsDir() && strings.HasSuffix(f.Name(), ".go") {
+ data, err := ioutil.ReadFile(filepath.Join(pkg.Pth, f.Name()))
+ boom(err)
+ if reRegisterEndpointsService.Match(data) {
+ pkgs <- pkgOk{pkg, true}
+ return
+ }
+ }
+ }
+ pkgs <- pkgOk{pkg, false}
+ }()
+ }
+ go func() {
+ wg.Wait()
+ close(pkgs)
+ }()
+
+ for p := range pkgs {
+ if !p.ok {
+ if *verbose {
+ fmt.Println("skipping package", p.p.Imp, "(it doesn't impliment RegisterEndpointsService?)")
+ }
+ } else {
+ fmt.Println("including package", p.p.Imp)
+ ret = append(ret, p.p)
+ }
+ }
+
+ return ret
+}
+
+func writeFiles(dir string, pkgs []Pkg) {
+ input := &templInput{pkgs, time.Now()}
+ for _, ext := range []string{"go", "yaml"} {
+ buf := &bytes.Buffer{}
+ boom(templ.ExecuteTemplate(buf, ext, input))
+ boom(ioutil.WriteFile(filepath.Join(dir, "app."+ext), buf.Bytes(), 0666))
+ }
+}
+
+func startServer(dir string) (func(), func()) {
+ args := []string{"serve"}
+ if *clearDatastore {
+ args = append(args, "-clear_datastore")
+ }
+ args = append(args, dir)
+ server := exec.Command("goapp", args...)
+ server.Stdout = os.Stdout
+ server.Stderr = os.Stderr
+ boom(server.Start())
+
+ wait := func() { server.Wait() }
+ stop := func() {
+ server.Process.Signal(os.Interrupt)
+ wait()
+ }
+ return stop, wait
+}
+
+func main() {
+ _, err := exec.LookPath("goapp")
+ if err != nil {
+ panic("goapp must be on your path")
+ }
+
+ flag.Parse()
+ pkgs := getPkgs()
+
+ dir, err := ioutil.TempDir("", "epservice_gen")
+ boom(err)
+ prefix := "LEAKING"
+ if !*leak {
+ prefix = "generating"
+ defer os.RemoveAll(dir)
+ }
+ fmt.Println(prefix, "files in:", dir)
+
+ writeFiles(dir, pkgs)
+
+ stop, wait := startServer(dir)
+ defer wait()
+
+ intC := make(chan os.Signal, 1)
dnj 2015/06/09 02:56:58 Way less cute of a channel name :(
+ signal.Notify(intC, os.Interrupt, os.Kill)
+ sigC := make(chan struct{})
+ go func() {
+ defer close(sigC)
+ <-intC
+ stop()
+ }()
+
+ waitC := make(chan struct{})
+ go func() {
+ defer close(waitC)
+ wait()
+ }()
+
+ select {
+ case <-sigC:
+ case <-waitC:
+ }
+ time.Sleep(time.Second)
dnj 2015/06/09 02:56:58 Why is this necessary?
iannucci 2015/06/09 07:41:11 added it while I was debugging something else. The
+}
« no previous file with comments | « go/src/infra/gae/epclient/epclient.go ('k') | go/src/infra/gae/epservice/example/model.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698