Merge "TBR advance appengine to HEAD"
diff --git a/go/src/google.golang.org/appengine/.travis.yml b/go/src/google.golang.org/appengine/.travis.yml
index bfa8658..435bf94 100644
--- a/go/src/google.golang.org/appengine/.travis.yml
+++ b/go/src/google.golang.org/appengine/.travis.yml
@@ -1,14 +1,17 @@
language: go
+sudo: false
go:
- 1.4
install:
- - export GOPATH="$HOME/gopath"
- - mkdir -p "$GOPATH/src/google.golang.org"
- - mv "$TRAVIS_BUILD_DIR" "$GOPATH/src/google.golang.org/appengine"
- go get -v -t -d google.golang.org/appengine/...
+ - mkdir sdk
+ - curl -o sdk.zip "https://storage.googleapis.com/appengine-sdks/featured/go_appengine_sdk_linux_amd64-1.9.24.zip"
+ - unzip sdk.zip -d sdk
script:
+ - go version
- go test -v google.golang.org/appengine/...
- go test -v -race google.golang.org/appengine/...
+ - sdk/go_appengine/goapp test -v google.golang.org/appengine/...
\ No newline at end of file
diff --git a/go/src/google.golang.org/appengine/README.google b/go/src/google.golang.org/appengine/README.google
index 014d762..451fbee 100644
--- a/go/src/google.golang.org/appengine/README.google
+++ b/go/src/google.golang.org/appengine/README.google
@@ -1,5 +1,5 @@
-URL: https://github.com/golang/appengine/archive/5331974dffbe7636afb62f4529b4f204886b29f0.zip
-Version: 5331974dffbe7636afb62f4529b4f204886b29f0
+URL: https://github.com/golang/appengine/archive/d2b1961e346073b2ce8710267c92e4ff4cc0d099.zip
+Version: d2b1961e346073b2ce8710267c92e4ff4cc0d099
License: Apache 2.0
License File: LICENSE
diff --git a/go/src/google.golang.org/appengine/README.md b/go/src/google.golang.org/appengine/README.md
index 4b22bde..096aaa7 100644
--- a/go/src/google.golang.org/appengine/README.md
+++ b/go/src/google.golang.org/appengine/README.md
@@ -56,13 +56,16 @@
* `appengine.Timeout` has been removed. Use `context.WithTimeout` instead.
* `appengine.Datacenter` now takes a `context.Context` argument.
* `datastore.PropertyLoadSaver` has been simplified to use slices in place of channels.
+* `delay.Call` now returns an error.
* `search.FieldLoadSaver` now handles document metadata.
+* `urlfetch.Transport` no longer has a Deadline field; set a deadline on the
+ `context.Context` instead.
* `taskqueue.QueueStats` no longer takes a maxTasks argument. That argument has been
deprecated and unused for a long time.
-* `appengine/aetest`, `appengine/blobstore`, `appengine/cloudsql`
- and `appengine/runtime` have not been ported yet.
+* `appengine/aetest`, `appengine/cloudsql` and `appengine/runtime` have not been ported yet.
* `appengine.BackendHostname` and `appengine.BackendInstance` were for the deprecated backends feature.
Use `appengine.ModuleHostname`and `appengine.ModuleName` instead.
* `appengine.IsCapabilityDisabled` and `appengine/capability` are obsolete.
-* Most of `appengine/file` is deprecated. Use [Google Cloud Storage](https://godoc.org/google.golang.org/cloud/storage) instead.
-* `appengine/socket` is deprecated. Use the standard `net` package instead.
+* Most of `appengine/file` and parts of `appengine/blobstore` are deprecated.
+ Use [Google Cloud Storage](https://godoc.org/google.golang.org/cloud/storage) instead.
+* `appengine/socket` is not required on Managed VMs. Use the standard `net` package instead.
diff --git a/go/src/google.golang.org/appengine/appengine.go b/go/src/google.golang.org/appengine/appengine.go
index ccd1f82..e455413 100644
--- a/go/src/google.golang.org/appengine/appengine.go
+++ b/go/src/google.golang.org/appengine/appengine.go
@@ -19,20 +19,24 @@
// IsDevAppServer reports whether the App Engine app is running in the
// development App Server.
func IsDevAppServer() bool {
- // TODO(dsymonds): Detect this.
- return false
+ return internal.IsDevAppServer()
}
// NewContext returns a context for an in-flight HTTP request.
// This function is cheap.
func NewContext(req *http.Request) context.Context {
- return internal.NewContext(req)
+ return WithContext(context.Background(), req)
+}
+
+// WithContext returns a copy of the parent context
+// and associates it with an in-flight HTTP request.
+// This function is cheap.
+func WithContext(parent context.Context, req *http.Request) context.Context {
+ return internal.WithContext(parent, req)
}
// TODO(dsymonds): Add a Call function here? Otherwise other packages can't access internal.Call.
-// TODO(dsymonds): Add BackgroundContext function?
-
// BlobKey is a key for a blobstore blob.
//
// Conceptually, this type belongs in the blobstore package, but it lives in
diff --git a/go/src/google.golang.org/appengine/appengine_test.go b/go/src/google.golang.org/appengine/appengine_test.go
index e1cfb82..f1cf0a1 100644
--- a/go/src/google.golang.org/appengine/appengine_test.go
+++ b/go/src/google.golang.org/appengine/appengine_test.go
@@ -1,3 +1,7 @@
+// Copyright 2014 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
package appengine
import (
diff --git a/go/src/google.golang.org/appengine/appengine_vm.go b/go/src/google.golang.org/appengine/appengine_vm.go
new file mode 100644
index 0000000..7648196
--- /dev/null
+++ b/go/src/google.golang.org/appengine/appengine_vm.go
@@ -0,0 +1,32 @@
+// Copyright 2015 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+// +build !appengine
+
+package appengine
+
+import (
+ "golang.org/x/net/context"
+
+ "google.golang.org/appengine/internal"
+)
+
+// The comment below must not be changed.
+// It is used by go-app-builder to recognise that this package has
+// the Main function to use in the synthetic main.
+// The gophers party all night; the rabbits provide the beats.
+
+// Main installs the health checker and creates a server listening on port
+// "PORT" if set in the environment or on port 8080.
+// It uses the default http handler and never returns.
+func Main() {
+ internal.Main()
+}
+
+// BackgroundContext returns a context not associated with a request.
+// This should only be used when not servicing a request.
+// This only works on Managed VMs.
+func BackgroundContext() context.Context {
+ return internal.BackgroundContext()
+}
diff --git a/go/src/google.golang.org/appengine/blobstore/blobstore.go b/go/src/google.golang.org/appengine/blobstore/blobstore.go
new file mode 100644
index 0000000..1c8087b
--- /dev/null
+++ b/go/src/google.golang.org/appengine/blobstore/blobstore.go
@@ -0,0 +1,276 @@
+// Copyright 2011 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+// Package blobstore provides a client for App Engine's persistent blob
+// storage service.
+package blobstore // import "google.golang.org/appengine/blobstore"
+
+import (
+ "bufio"
+ "encoding/base64"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "mime"
+ "mime/multipart"
+ "net/http"
+ "net/textproto"
+ "net/url"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/golang/protobuf/proto"
+ "golang.org/x/net/context"
+
+ "google.golang.org/appengine"
+ "google.golang.org/appengine/datastore"
+ "google.golang.org/appengine/internal"
+
+ basepb "google.golang.org/appengine/internal/base"
+ blobpb "google.golang.org/appengine/internal/blobstore"
+)
+
+const (
+ blobInfoKind = "__BlobInfo__"
+ blobFileIndexKind = "__BlobFileIndex__"
+ zeroKey = appengine.BlobKey("")
+)
+
+// BlobInfo is the blob metadata that is stored in the datastore.
+// Filename may be empty.
+type BlobInfo struct {
+ BlobKey appengine.BlobKey
+ ContentType string `datastore:"content_type"`
+ CreationTime time.Time `datastore:"creation"`
+ Filename string `datastore:"filename"`
+ Size int64 `datastore:"size"`
+ MD5 string `datastore:"md5_hash"`
+
+ // ObjectName is the Google Cloud Storage name for this blob.
+ ObjectName string `datastore:"gs_object_name"`
+}
+
+// isErrFieldMismatch returns whether err is a datastore.ErrFieldMismatch.
+//
+// The blobstore stores blob metadata in the datastore. When loading that
+// metadata, it may contain fields that we don't care about. datastore.Get will
+// return datastore.ErrFieldMismatch in that case, so we ignore that specific
+// error.
+func isErrFieldMismatch(err error) bool {
+ _, ok := err.(*datastore.ErrFieldMismatch)
+ return ok
+}
+
+// Stat returns the BlobInfo for a provided blobKey. If no blob was found for
+// that key, Stat returns datastore.ErrNoSuchEntity.
+func Stat(c context.Context, blobKey appengine.BlobKey) (*BlobInfo, error) {
+ c, _ = appengine.Namespace(c, "") // Blobstore is always in the empty string namespace
+ dskey := datastore.NewKey(c, blobInfoKind, string(blobKey), 0, nil)
+ bi := &BlobInfo{
+ BlobKey: blobKey,
+ }
+ if err := datastore.Get(c, dskey, bi); err != nil && !isErrFieldMismatch(err) {
+ return nil, err
+ }
+ return bi, nil
+}
+
+// Send sets the headers on response to instruct App Engine to send a blob as
+// the response body. This is more efficient than reading and writing it out
+// manually and isn't subject to normal response size limits.
+func Send(response http.ResponseWriter, blobKey appengine.BlobKey) {
+ hdr := response.Header()
+ hdr.Set("X-AppEngine-BlobKey", string(blobKey))
+
+ if hdr.Get("Content-Type") == "" {
+ // This value is known to dev_appserver to mean automatic.
+ // In production this is remapped to the empty value which
+ // means automatic.
+ hdr.Set("Content-Type", "application/vnd.google.appengine.auto")
+ }
+}
+
+// UploadURL creates an upload URL for the form that the user will
+// fill out, passing the application path to load when the POST of the
+// form is completed. These URLs expire and should not be reused. The
+// opts parameter may be nil.
+func UploadURL(c context.Context, successPath string, opts *UploadURLOptions) (*url.URL, error) {
+ req := &blobpb.CreateUploadURLRequest{
+ SuccessPath: proto.String(successPath),
+ }
+ if opts != nil {
+ if n := opts.MaxUploadBytes; n != 0 {
+ req.MaxUploadSizeBytes = &n
+ }
+ if n := opts.MaxUploadBytesPerBlob; n != 0 {
+ req.MaxUploadSizePerBlobBytes = &n
+ }
+ if s := opts.StorageBucket; s != "" {
+ req.GsBucketName = &s
+ }
+ }
+ res := &blobpb.CreateUploadURLResponse{}
+ if err := internal.Call(c, "blobstore", "CreateUploadURL", req, res); err != nil {
+ return nil, err
+ }
+ return url.Parse(*res.Url)
+}
+
+// UploadURLOptions are the options to create an upload URL.
+type UploadURLOptions struct {
+ MaxUploadBytes int64 // optional
+ MaxUploadBytesPerBlob int64 // optional
+
+ // StorageBucket specifies the Google Cloud Storage bucket in which
+ // to store the blob.
+ // This is required if you use Cloud Storage instead of Blobstore.
+ // Your application must have permission to write to the bucket.
+ // You may optionally specify a bucket name and path in the format
+ // "bucket_name/path", in which case the included path will be the
+ // prefix of the uploaded object's name.
+ StorageBucket string
+}
+
+// Delete deletes a blob.
+func Delete(c context.Context, blobKey appengine.BlobKey) error {
+ return DeleteMulti(c, []appengine.BlobKey{blobKey})
+}
+
+// DeleteMulti deletes multiple blobs.
+func DeleteMulti(c context.Context, blobKey []appengine.BlobKey) error {
+ s := make([]string, len(blobKey))
+ for i, b := range blobKey {
+ s[i] = string(b)
+ }
+ req := &blobpb.DeleteBlobRequest{
+ BlobKey: s,
+ }
+ res := &basepb.VoidProto{}
+ if err := internal.Call(c, "blobstore", "DeleteBlob", req, res); err != nil {
+ return err
+ }
+ return nil
+}
+
+func errorf(format string, args ...interface{}) error {
+ return fmt.Errorf("blobstore: "+format, args...)
+}
+
+// ParseUpload parses the synthetic POST request that your app gets from
+// App Engine after a user's successful upload of blobs. Given the request,
+// ParseUpload returns a map of the blobs received (keyed by HTML form
+// element name) and other non-blob POST parameters.
+func ParseUpload(req *http.Request) (blobs map[string][]*BlobInfo, other url.Values, err error) {
+ _, params, err := mime.ParseMediaType(req.Header.Get("Content-Type"))
+ if err != nil {
+ return nil, nil, err
+ }
+ boundary := params["boundary"]
+ if boundary == "" {
+ return nil, nil, errorf("did not find MIME multipart boundary")
+ }
+
+ blobs = make(map[string][]*BlobInfo)
+ other = make(url.Values)
+
+ mreader := multipart.NewReader(io.MultiReader(req.Body, strings.NewReader("\r\n\r\n")), boundary)
+ for {
+ part, perr := mreader.NextPart()
+ if perr == io.EOF {
+ break
+ }
+ if perr != nil {
+ return nil, nil, errorf("error reading next mime part with boundary %q (len=%d): %v",
+ boundary, len(boundary), perr)
+ }
+
+ bi := &BlobInfo{}
+ ctype, params, err := mime.ParseMediaType(part.Header.Get("Content-Disposition"))
+ if err != nil {
+ return nil, nil, err
+ }
+ bi.Filename = params["filename"]
+ formKey := params["name"]
+
+ ctype, params, err = mime.ParseMediaType(part.Header.Get("Content-Type"))
+ if err != nil {
+ return nil, nil, err
+ }
+ bi.BlobKey = appengine.BlobKey(params["blob-key"])
+ if ctype != "message/external-body" || bi.BlobKey == "" {
+ if formKey != "" {
+ slurp, serr := ioutil.ReadAll(part)
+ if serr != nil {
+ return nil, nil, errorf("error reading %q MIME part", formKey)
+ }
+ other[formKey] = append(other[formKey], string(slurp))
+ }
+ continue
+ }
+
+ // App Engine sends a MIME header as the body of each MIME part.
+ tp := textproto.NewReader(bufio.NewReader(part))
+ header, mimeerr := tp.ReadMIMEHeader()
+ if mimeerr != nil {
+ return nil, nil, mimeerr
+ }
+ bi.Size, err = strconv.ParseInt(header.Get("Content-Length"), 10, 64)
+ if err != nil {
+ return nil, nil, err
+ }
+ bi.ContentType = header.Get("Content-Type")
+
+ // Parse the time from the MIME header like:
+ // X-AppEngine-Upload-Creation: 2011-03-15 21:38:34.712136
+ createDate := header.Get("X-AppEngine-Upload-Creation")
+ if createDate == "" {
+ return nil, nil, errorf("expected to find an X-AppEngine-Upload-Creation header")
+ }
+ bi.CreationTime, err = time.Parse("2006-01-02 15:04:05.000000", createDate)
+ if err != nil {
+ return nil, nil, errorf("error parsing X-AppEngine-Upload-Creation: %s", err)
+ }
+
+ if hdr := header.Get("Content-MD5"); hdr != "" {
+ md5, err := base64.URLEncoding.DecodeString(hdr)
+ if err != nil {
+ return nil, nil, errorf("bad Content-MD5 %q: %v", hdr, err)
+ }
+ bi.MD5 = string(md5)
+ }
+
+ // If the GCS object name was provided, record it.
+ bi.ObjectName = header.Get("X-AppEngine-Cloud-Storage-Object")
+
+ blobs[formKey] = append(blobs[formKey], bi)
+ }
+ return
+}
+
+// Reader is a blob reader.
+type Reader interface {
+ io.Reader
+ io.ReaderAt
+ io.Seeker
+}
+
+// NewReader returns a reader for a blob. It always succeeds; if the blob does
+// not exist then an error will be reported upon first read.
+func NewReader(c context.Context, blobKey appengine.BlobKey) Reader {
+ return openBlob(c, blobKey)
+}
+
+// BlobKeyForFile returns a BlobKey for a Google Storage file.
+// The filename should be of the form "/gs/bucket_name/object_name".
+func BlobKeyForFile(c context.Context, filename string) (appengine.BlobKey, error) {
+ req := &blobpb.CreateEncodedGoogleStorageKeyRequest{
+ Filename: &filename,
+ }
+ res := &blobpb.CreateEncodedGoogleStorageKeyResponse{}
+ if err := internal.Call(c, "blobstore", "CreateEncodedGoogleStorageKey", req, res); err != nil {
+ return "", err
+ }
+ return appengine.BlobKey(*res.BlobKey), nil
+}
diff --git a/go/src/google.golang.org/appengine/blobstore/blobstore_test.go b/go/src/google.golang.org/appengine/blobstore/blobstore_test.go
new file mode 100644
index 0000000..c2be7ef
--- /dev/null
+++ b/go/src/google.golang.org/appengine/blobstore/blobstore_test.go
@@ -0,0 +1,183 @@
+// Copyright 2011 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package blobstore
+
+import (
+ "io"
+ "os"
+ "strconv"
+ "strings"
+ "testing"
+
+ "google.golang.org/appengine"
+ "google.golang.org/appengine/internal/aetesting"
+
+ pb "google.golang.org/appengine/internal/blobstore"
+)
+
+const rbs = readBufferSize
+
+func min(x, y int) int {
+ if x < y {
+ return x
+ }
+ return y
+}
+
+func fakeFetchData(req *pb.FetchDataRequest, res *pb.FetchDataResponse) error {
+ i0 := int(*req.StartIndex)
+ i1 := int(*req.EndIndex + 1) // Blobstore's end-indices are inclusive; Go's are exclusive.
+ bk := *req.BlobKey
+ if i := strings.Index(bk, "."); i != -1 {
+ // Strip everything past the ".".
+ bk = bk[:i]
+ }
+ switch bk {
+ case "a14p":
+ const s = "abcdefghijklmnop"
+ i0 := min(len(s), i0)
+ i1 := min(len(s), i1)
+ res.Data = []byte(s[i0:i1])
+ case "longBlob":
+ res.Data = make([]byte, i1-i0)
+ for i := range res.Data {
+ res.Data[i] = 'A' + uint8(i0/rbs)
+ i0++
+ }
+ }
+ return nil
+}
+
+// step is one step of a readerTest.
+// It consists of a Reader method to call, the method arguments
+// (lenp, offset, whence) and the expected results.
+type step struct {
+ method string
+ lenp int
+ offset int64
+ whence int
+ want string
+ wantErr error
+}
+
+var readerTest = []struct {
+ blobKey string
+ step []step
+}{
+ {"noSuchBlobKey", []step{
+ {"Read", 8, 0, 0, "", io.EOF},
+ }},
+ {"a14p.0", []step{
+ // Test basic reads.
+ {"Read", 1, 0, 0, "a", nil},
+ {"Read", 3, 0, 0, "bcd", nil},
+ {"Read", 1, 0, 0, "e", nil},
+ {"Read", 2, 0, 0, "fg", nil},
+ // Test Seek.
+ {"Seek", 0, 2, os.SEEK_SET, "2", nil},
+ {"Read", 5, 0, 0, "cdefg", nil},
+ {"Seek", 0, 2, os.SEEK_CUR, "9", nil},
+ {"Read", 1, 0, 0, "j", nil},
+ // Test reads up to and past EOF.
+ {"Read", 5, 0, 0, "klmno", nil},
+ {"Read", 5, 0, 0, "p", nil},
+ {"Read", 5, 0, 0, "", io.EOF},
+ // Test ReadAt.
+ {"ReadAt", 4, 0, 0, "abcd", nil},
+ {"ReadAt", 4, 3, 0, "defg", nil},
+ {"ReadAt", 4, 12, 0, "mnop", nil},
+ {"ReadAt", 4, 13, 0, "nop", io.EOF},
+ {"ReadAt", 4, 99, 0, "", io.EOF},
+ }},
+ {"a14p.1", []step{
+ // Test Seek before any reads.
+ {"Seek", 0, 2, os.SEEK_SET, "2", nil},
+ {"Read", 1, 0, 0, "c", nil},
+ // Test that ReadAt doesn't affect the Read offset.
+ {"ReadAt", 3, 9, 0, "jkl", nil},
+ {"Read", 3, 0, 0, "def", nil},
+ }},
+ {"a14p.2", []step{
+ // Test ReadAt before any reads or seeks.
+ {"ReadAt", 2, 14, 0, "op", nil},
+ }},
+ {"longBlob.0", []step{
+ // Test basic read.
+ {"Read", 1, 0, 0, "A", nil},
+ // Test that Read returns early when the buffer is exhausted.
+ {"Seek", 0, rbs - 2, os.SEEK_SET, strconv.Itoa(rbs - 2), nil},
+ {"Read", 5, 0, 0, "AA", nil},
+ {"Read", 3, 0, 0, "BBB", nil},
+ // Test that what we just read is still in the buffer.
+ {"Seek", 0, rbs - 2, os.SEEK_SET, strconv.Itoa(rbs - 2), nil},
+ {"Read", 5, 0, 0, "AABBB", nil},
+ // Test ReadAt.
+ {"ReadAt", 3, rbs - 4, 0, "AAA", nil},
+ {"ReadAt", 6, rbs - 4, 0, "AAAABB", nil},
+ {"ReadAt", 8, rbs - 4, 0, "AAAABBBB", nil},
+ {"ReadAt", 5, rbs - 4, 0, "AAAAB", nil},
+ {"ReadAt", 2, rbs - 4, 0, "AA", nil},
+ // Test seeking backwards from the Read offset.
+ {"Seek", 0, 2*rbs - 8, os.SEEK_SET, strconv.Itoa(2*rbs - 8), nil},
+ {"Read", 1, 0, 0, "B", nil},
+ {"Read", 1, 0, 0, "B", nil},
+ {"Read", 1, 0, 0, "B", nil},
+ {"Read", 1, 0, 0, "B", nil},
+ {"Read", 8, 0, 0, "BBBBCCCC", nil},
+ }},
+ {"longBlob.1", []step{
+ // Test ReadAt with a slice larger than the buffer size.
+ {"LargeReadAt", 2*rbs - 2, 0, 0, strconv.Itoa(2*rbs - 2), nil},
+ {"LargeReadAt", 2*rbs - 1, 0, 0, strconv.Itoa(2*rbs - 1), nil},
+ {"LargeReadAt", 2*rbs + 0, 0, 0, strconv.Itoa(2*rbs + 0), nil},
+ {"LargeReadAt", 2*rbs + 1, 0, 0, strconv.Itoa(2*rbs + 1), nil},
+ {"LargeReadAt", 2*rbs + 2, 0, 0, strconv.Itoa(2*rbs + 2), nil},
+ {"LargeReadAt", 2*rbs - 2, 1, 0, strconv.Itoa(2*rbs - 2), nil},
+ {"LargeReadAt", 2*rbs - 1, 1, 0, strconv.Itoa(2*rbs - 1), nil},
+ {"LargeReadAt", 2*rbs + 0, 1, 0, strconv.Itoa(2*rbs + 0), nil},
+ {"LargeReadAt", 2*rbs + 1, 1, 0, strconv.Itoa(2*rbs + 1), nil},
+ {"LargeReadAt", 2*rbs + 2, 1, 0, strconv.Itoa(2*rbs + 2), nil},
+ }},
+}
+
+func TestReader(t *testing.T) {
+ for _, rt := range readerTest {
+ c := aetesting.FakeSingleContext(t, "blobstore", "FetchData", fakeFetchData)
+ r := NewReader(c, appengine.BlobKey(rt.blobKey))
+ for i, step := range rt.step {
+ var (
+ got string
+ gotErr error
+ n int
+ offset int64
+ )
+ switch step.method {
+ case "LargeReadAt":
+ p := make([]byte, step.lenp)
+ n, gotErr = r.ReadAt(p, step.offset)
+ got = strconv.Itoa(n)
+ case "Read":
+ p := make([]byte, step.lenp)
+ n, gotErr = r.Read(p)
+ got = string(p[:n])
+ case "ReadAt":
+ p := make([]byte, step.lenp)
+ n, gotErr = r.ReadAt(p, step.offset)
+ got = string(p[:n])
+ case "Seek":
+ offset, gotErr = r.Seek(step.offset, step.whence)
+ got = strconv.FormatInt(offset, 10)
+ default:
+ t.Fatalf("unknown method: %s", step.method)
+ }
+ if gotErr != step.wantErr {
+ t.Fatalf("%s step %d: got error %v want %v", rt.blobKey, i, gotErr, step.wantErr)
+ }
+ if got != step.want {
+ t.Fatalf("%s step %d: got %q want %q", rt.blobKey, i, got, step.want)
+ }
+ }
+ }
+}
diff --git a/go/src/google.golang.org/appengine/blobstore/read.go b/go/src/google.golang.org/appengine/blobstore/read.go
new file mode 100644
index 0000000..578b1f5
--- /dev/null
+++ b/go/src/google.golang.org/appengine/blobstore/read.go
@@ -0,0 +1,160 @@
+// Copyright 2012 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package blobstore
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "sync"
+
+ "github.com/golang/protobuf/proto"
+ "golang.org/x/net/context"
+
+ "google.golang.org/appengine"
+ "google.golang.org/appengine/internal"
+
+ blobpb "google.golang.org/appengine/internal/blobstore"
+)
+
+// openBlob returns a reader for a blob. It always succeeds; if the blob does
+// not exist then an error will be reported upon first read.
+func openBlob(c context.Context, blobKey appengine.BlobKey) Reader {
+ return &reader{
+ c: c,
+ blobKey: blobKey,
+ }
+}
+
+const readBufferSize = 256 * 1024
+
+// reader is a blob reader. It implements the Reader interface.
+type reader struct {
+ c context.Context
+
+ // Either blobKey or filename is set:
+ blobKey appengine.BlobKey
+ filename string
+
+ closeFunc func() // is nil if unavailable or already closed.
+
+ // buf is the read buffer. r is how much of buf has been read.
+ // off is the offset of buf[0] relative to the start of the blob.
+ // An invariant is 0 <= r && r <= len(buf).
+ // Reads that don't require an RPC call will increment r but not off.
+ // Seeks may modify r without discarding the buffer, but only if the
+ // invariant can be maintained.
+ mu sync.Mutex
+ buf []byte
+ r int
+ off int64
+}
+
+func (r *reader) Close() error {
+ if f := r.closeFunc; f != nil {
+ f()
+ }
+ r.closeFunc = nil
+ return nil
+}
+
+func (r *reader) Read(p []byte) (int, error) {
+ if len(p) == 0 {
+ return 0, nil
+ }
+ r.mu.Lock()
+ defer r.mu.Unlock()
+ if r.r == len(r.buf) {
+ if err := r.fetch(r.off + int64(r.r)); err != nil {
+ return 0, err
+ }
+ }
+ n := copy(p, r.buf[r.r:])
+ r.r += n
+ return n, nil
+}
+
+func (r *reader) ReadAt(p []byte, off int64) (int, error) {
+ if len(p) == 0 {
+ return 0, nil
+ }
+ r.mu.Lock()
+ defer r.mu.Unlock()
+ // Convert relative offsets to absolute offsets.
+ ab0 := r.off + int64(r.r)
+ ab1 := r.off + int64(len(r.buf))
+ ap0 := off
+ ap1 := off + int64(len(p))
+ // Check if we can satisfy the read entirely out of the existing buffer.
+ if r.off <= ap0 && ap1 <= ab1 {
+ // Convert off from an absolute offset to a relative offset.
+ rp0 := int(ap0 - r.off)
+ return copy(p, r.buf[rp0:]), nil
+ }
+ // Restore the original Read/Seek offset after ReadAt completes.
+ defer r.seek(ab0)
+ // Repeatedly fetch and copy until we have filled p.
+ n := 0
+ for len(p) > 0 {
+ if err := r.fetch(off + int64(n)); err != nil {
+ return n, err
+ }
+ r.r = copy(p, r.buf)
+ n += r.r
+ p = p[r.r:]
+ }
+ return n, nil
+}
+
+func (r *reader) Seek(offset int64, whence int) (ret int64, err error) {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+ switch whence {
+ case os.SEEK_SET:
+ ret = offset
+ case os.SEEK_CUR:
+ ret = r.off + int64(r.r) + offset
+ case os.SEEK_END:
+ return 0, errors.New("seeking relative to the end of a blob isn't supported")
+ default:
+ return 0, fmt.Errorf("invalid Seek whence value: %d", whence)
+ }
+ if ret < 0 {
+ return 0, errors.New("negative Seek offset")
+ }
+ return r.seek(ret)
+}
+
+// fetch fetches readBufferSize bytes starting at the given offset. On success,
+// the data is saved as r.buf.
+func (r *reader) fetch(off int64) error {
+ req := &blobpb.FetchDataRequest{
+ BlobKey: proto.String(string(r.blobKey)),
+ StartIndex: proto.Int64(off),
+ EndIndex: proto.Int64(off + readBufferSize - 1), // EndIndex is inclusive.
+ }
+ res := &blobpb.FetchDataResponse{}
+ if err := internal.Call(r.c, "blobstore", "FetchData", req, res); err != nil {
+ return err
+ }
+ if len(res.Data) == 0 {
+ return io.EOF
+ }
+ r.buf, r.r, r.off = res.Data, 0, off
+ return nil
+}
+
+// seek seeks to the given offset with an effective whence equal to SEEK_SET.
+// It discards the read buffer if the invariant cannot be maintained.
+func (r *reader) seek(off int64) (int64, error) {
+ delta := off - r.off
+ if delta >= 0 && delta < int64(len(r.buf)) {
+ r.r = int(delta)
+ return off, nil
+ }
+ r.buf, r.r, r.off = nil, 0, off
+ return off, nil
+}
diff --git a/go/src/google.golang.org/appengine/channel/channel_test.go b/go/src/google.golang.org/appengine/channel/channel_test.go
index d047337..c7498eb 100644
--- a/go/src/google.golang.org/appengine/channel/channel_test.go
+++ b/go/src/google.golang.org/appengine/channel/channel_test.go
@@ -1,3 +1,7 @@
+// Copyright 2015 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
package channel
import (
diff --git a/go/src/google.golang.org/appengine/cmd/aebundler/aebundler.go b/go/src/google.golang.org/appengine/cmd/aebundler/aebundler.go
new file mode 100644
index 0000000..9080ce2
--- /dev/null
+++ b/go/src/google.golang.org/appengine/cmd/aebundler/aebundler.go
@@ -0,0 +1,342 @@
+// Copyright 2015 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+// Program aebundler turns a Go app into a fully self-contained tar file.
+// The app and its subdirectories (if any) are placed under "."
+// and the dependencies from $GOPATH are placed under ./_gopath/src.
+// A main func is synthesized if one does not exist.
+//
+// A sample Dockerfile to be used with this bundler could look like this:
+// FROM gcr.io/google_appengine/go-compat
+// ADD . /app
+// RUN GOPATH=/app/_gopath go build -tags appenginevm -o /app/_ah/exe
+package main
+
+import (
+ "archive/tar"
+ "flag"
+ "fmt"
+ "go/ast"
+ "go/build"
+ "go/parser"
+ "go/token"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+var (
+ output = flag.String("o", "", "name of output tar file or '-' for stdout")
+ rootDir = flag.String("root", ".", "directory name of application root")
+ vm = flag.Bool("vm", true, "bundle a Managed VM app")
+
+ skipFiles = map[string]bool{
+ ".git": true,
+ ".gitconfig": true,
+ ".hg": true,
+ ".travis.yml": true,
+ }
+)
+
+const (
+ newMain = `package main
+import "google.golang.org/appengine"
+func main() {
+ appengine.Main()
+}
+`
+)
+
+func usage() {
+ fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
+ fmt.Fprintf(os.Stderr, "\t%s -o <file.tar|->\tBundle app to named tar file or stdout\n", os.Args[0])
+ fmt.Fprintf(os.Stderr, "\noptional arguments:\n")
+ flag.PrintDefaults()
+}
+
+func main() {
+ flag.Usage = usage
+ flag.Parse()
+
+ var tags []string
+ if *vm {
+ tags = append(tags, "appenginevm")
+ } else {
+ tags = append(tags, "appengine")
+ }
+
+ tarFile := *output
+ if tarFile == "" {
+ usage()
+ errorf("Required -o flag not specified.")
+ }
+
+ app, err := analyze(tags)
+ if err != nil {
+ errorf("Error analyzing app: %v", err)
+ }
+ if err := app.bundle(tarFile); err != nil {
+ errorf("Unable to bundle app: %v", err)
+ }
+}
+
+// errorf prints the error message and exits.
+func errorf(format string, a ...interface{}) {
+ fmt.Fprintf(os.Stderr, "aebundler: "+format+"\n", a...)
+ os.Exit(1)
+}
+
+type app struct {
+ hasMain bool
+ appFiles []string
+ imports map[string]string
+}
+
+// analyze checks the app for building with the given build tags and returns hasMain,
+// app files, and a map of full directory import names to original import names.
+func analyze(tags []string) (*app, error) {
+ ctxt := buildContext(tags)
+ hasMain, appFiles, err := checkMain(ctxt)
+ if err != nil {
+ return nil, err
+ }
+ gopath := filepath.SplitList(ctxt.GOPATH)
+ im, err := imports(ctxt, *rootDir, gopath)
+ return &app{
+ hasMain: hasMain,
+ appFiles: appFiles,
+ imports: im,
+ }, err
+}
+
+// buildContext returns the context for building the source.
+func buildContext(tags []string) *build.Context {
+ return &build.Context{
+ GOARCH: build.Default.GOARCH,
+ GOOS: build.Default.GOOS,
+ GOROOT: build.Default.GOROOT,
+ GOPATH: build.Default.GOPATH,
+ Compiler: build.Default.Compiler,
+ BuildTags: append(build.Default.BuildTags, tags...),
+ }
+}
+
+// bundle bundles the app into the named tarFile ("-"==stdout).
+func (s *app) bundle(tarFile string) (err error) {
+ var out io.Writer
+ if tarFile == "-" {
+ out = os.Stdout
+ } else {
+ f, err := os.Create(tarFile)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if cerr := f.Close(); err == nil {
+ err = cerr
+ }
+ }()
+ out = f
+ }
+ tw := tar.NewWriter(out)
+
+ for srcDir, importName := range s.imports {
+ dstDir := "_gopath/src/" + importName
+ if err = copyTree(tw, dstDir, srcDir); err != nil {
+ return fmt.Errorf("unable to copy directory %v to %v: %v", srcDir, dstDir, err)
+ }
+ }
+ if err := copyTree(tw, ".", *rootDir); err != nil {
+ return fmt.Errorf("unable to copy root directory to /app: %v", err)
+ }
+ if !s.hasMain {
+ if err := synthesizeMain(tw, s.appFiles); err != nil {
+ return fmt.Errorf("unable to synthesize new main func: %v", err)
+ }
+ }
+
+ if err := tw.Close(); err != nil {
+ return fmt.Errorf("unable to close tar file %v: %v", tarFile, err)
+ }
+ return nil
+}
+
+// synthesizeMain generates a new main func and writes it to the tarball.
+func synthesizeMain(tw *tar.Writer, appFiles []string) error {
+ appMap := make(map[string]bool)
+ for _, f := range appFiles {
+ appMap[f] = true
+ }
+ var f string
+ for i := 0; i < 100; i++ {
+ f = fmt.Sprintf("app_main%d.go", i)
+ if !appMap[filepath.Join(*rootDir, f)] {
+ break
+ }
+ }
+ if appMap[filepath.Join(*rootDir, f)] {
+ return fmt.Errorf("unable to find unique name for %v", f)
+ }
+ hdr := &tar.Header{
+ Name: f,
+ Mode: 0644,
+ Size: int64(len(newMain)),
+ }
+ if err := tw.WriteHeader(hdr); err != nil {
+ return fmt.Errorf("unable to write header for %v: %v", f, err)
+ }
+ if _, err := tw.Write([]byte(newMain)); err != nil {
+ return fmt.Errorf("unable to write %v to tar file: %v", f, err)
+ }
+ return nil
+}
+
+// imports returns a map of all import directories (recursively) used by the app.
+// The return value maps full directory names to original import names.
+func imports(ctxt *build.Context, srcDir string, gopath []string) (map[string]string, error) {
+ pkg, err := ctxt.ImportDir(srcDir, 0)
+ if err != nil {
+ return nil, fmt.Errorf("unable to analyze source: %v", err)
+ }
+
+ // Resolve all non-standard-library imports
+ result := make(map[string]string)
+ for _, v := range pkg.Imports {
+ if !strings.Contains(v, ".") {
+ continue
+ }
+ src, err := findInGopath(v, gopath)
+ if err != nil {
+ return nil, fmt.Errorf("unable to find import %v in gopath %v: %v", v, gopath, err)
+ }
+ result[src] = v
+ im, err := imports(ctxt, src, gopath)
+ if err != nil {
+ return nil, fmt.Errorf("unable to parse package %v: %v", src, err)
+ }
+ for k, v := range im {
+ result[k] = v
+ }
+ }
+ return result, nil
+}
+
+// findInGopath searches the gopath for the named import directory.
+func findInGopath(dir string, gopath []string) (string, error) {
+ for _, v := range gopath {
+ dst := filepath.Join(v, "src", dir)
+ if _, err := os.Stat(dst); err == nil {
+ return dst, nil
+ }
+ }
+ return "", fmt.Errorf("unable to find package %v in gopath %v", dir, gopath)
+}
+
+// copyTree copies srcDir to tar file dstDir, ignoring skipFiles.
+func copyTree(tw *tar.Writer, dstDir, srcDir string) error {
+ entries, err := ioutil.ReadDir(srcDir)
+ if err != nil {
+ return fmt.Errorf("unable to read dir %v: %v", srcDir, err)
+ }
+ for _, entry := range entries {
+ n := entry.Name()
+ if skipFiles[n] {
+ continue
+ }
+ s := filepath.Join(srcDir, n)
+ d := filepath.Join(dstDir, n)
+ if entry.IsDir() {
+ if err := copyTree(tw, d, s); err != nil {
+ return fmt.Errorf("unable to copy dir %v to %v: %v", s, d, err)
+ }
+ continue
+ }
+ if err := copyFile(tw, d, s); err != nil {
+ return fmt.Errorf("unable to copy dir %v to %v: %v", s, d, err)
+ }
+ }
+ return nil
+}
+
+// copyFile copies src to tar file dst.
+func copyFile(tw *tar.Writer, dst, src string) error {
+ s, err := os.Open(src)
+ if err != nil {
+ return fmt.Errorf("unable to open %v: %v", src, err)
+ }
+ defer s.Close()
+ fi, err := s.Stat()
+ if err != nil {
+ return fmt.Errorf("unable to stat %v: %v", src, err)
+ }
+
+ hdr, err := tar.FileInfoHeader(fi, dst)
+ if err != nil {
+ return fmt.Errorf("unable to create tar header for %v: %v", dst, err)
+ }
+ hdr.Name = dst
+ if err := tw.WriteHeader(hdr); err != nil {
+ return fmt.Errorf("unable to write header for %v: %v", dst, err)
+ }
+ _, err = io.Copy(tw, s)
+ if err != nil {
+ return fmt.Errorf("unable to copy %v to %v: %v", src, dst, err)
+ }
+ return nil
+}
+
+// checkMain verifies that there is a single "main" function.
+// It also returns a list of all Go source files in the app.
+func checkMain(ctxt *build.Context) (bool, []string, error) {
+ pkg, err := ctxt.ImportDir(*rootDir, 0)
+ if err != nil {
+ return false, nil, fmt.Errorf("unable to analyze source: %v", err)
+ }
+ if !pkg.IsCommand() {
+ errorf("Your app's package needs to be changed from %q to \"main\".\n", pkg.Name)
+ }
+ // Search for a "func main"
+ var hasMain bool
+ var appFiles []string
+ for _, f := range pkg.GoFiles {
+ n := filepath.Join(*rootDir, f)
+ appFiles = append(appFiles, n)
+ if hasMain, err = readFile(n); err != nil {
+ return false, nil, fmt.Errorf("error parsing %q: %v", n, err)
+ }
+ }
+ return hasMain, appFiles, nil
+}
+
+// isMain returns whether the given function declaration is a main function.
+// Such a function must be called "main", not have a receiver, and have no arguments or return types.
+func isMain(f *ast.FuncDecl) bool {
+ ft := f.Type
+ return f.Name.Name == "main" && f.Recv == nil && ft.Params.NumFields() == 0 && ft.Results.NumFields() == 0
+}
+
+// readFile reads and parses the Go source code file and returns whether it has a main function.
+func readFile(filename string) (hasMain bool, err error) {
+ var src []byte
+ src, err = ioutil.ReadFile(filename)
+ if err != nil {
+ return
+ }
+ fset := token.NewFileSet()
+ file, err := parser.ParseFile(fset, filename, src, 0)
+ for _, decl := range file.Decls {
+ funcDecl, ok := decl.(*ast.FuncDecl)
+ if !ok {
+ continue
+ }
+ if !isMain(funcDecl) {
+ continue
+ }
+ hasMain = true
+ break
+ }
+ return
+}
diff --git a/go/src/google.golang.org/appengine/cmd/aedeploy/aedeploy.go b/go/src/google.golang.org/appengine/cmd/aedeploy/aedeploy.go
new file mode 100644
index 0000000..11de9a3
--- /dev/null
+++ b/go/src/google.golang.org/appengine/cmd/aedeploy/aedeploy.go
@@ -0,0 +1,260 @@
+// Copyright 2015 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+// Program aedeploy assists with deploying Go Managed VM apps to production.
+// A temporary directory is created; the app, its subdirectories, and all its
+// dependencies from $GOPATH are copied into the directory; then the app
+// is deployed to production with the provided command.
+//
+// The app must be in "package main".
+//
+// This command must be issued from within the root directory of the app
+// (where the app.yaml file is located).
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "go/build"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+)
+
+var (
+ skipFiles = map[string]bool{
+ ".git": true,
+ ".gitconfig": true,
+ ".hg": true,
+ ".travis.yml": true,
+ }
+
+ gopathCache = map[string]string{}
+)
+
+func usage() {
+ fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
+ fmt.Fprintf(os.Stderr, "\t%s gcloud --verbosity debug preview app deploy --version myversion ./app.yaml\tDeploy app to production\n", os.Args[0])
+}
+
+func main() {
+ flag.Usage = usage
+ flag.Parse()
+ if flag.NArg() < 1 {
+ usage()
+ os.Exit(1)
+ }
+
+ if err := aedeploy(); err != nil {
+ fmt.Fprintf(os.Stderr, os.Args[0]+": Error: %v\n", err)
+ os.Exit(1)
+ }
+}
+
+func aedeploy() error {
+ tags := []string{"appenginevm"}
+ app, err := analyze(tags)
+ if err != nil {
+ return err
+ }
+
+ tmpDir, err := app.bundle()
+ if tmpDir != "" {
+ defer os.RemoveAll(tmpDir)
+ }
+ if err != nil {
+ return err
+ }
+
+ if err := os.Chdir(tmpDir); err != nil {
+ return fmt.Errorf("unable to chdir to %v: %v", tmpDir, err)
+ }
+ return deploy()
+}
+
+// deploy calls the provided command to deploy the app from the temporary directory.
+func deploy() error {
+ cmd := exec.Command(flag.Arg(0), flag.Args()[1:]...)
+ cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("unable to run %q: %v", strings.Join(flag.Args(), " "), err)
+ }
+ return nil
+}
+
+type app struct {
+ appFiles []string
+ imports map[string]string
+}
+
+// analyze checks the app for building with the given build tags and returns
+// app files, and a map of full directory import names to original import names.
+func analyze(tags []string) (*app, error) {
+ ctxt := buildContext(tags)
+ appFiles, err := appFiles(ctxt)
+ if err != nil {
+ return nil, err
+ }
+ gopath := filepath.SplitList(ctxt.GOPATH)
+ im, err := imports(ctxt, ".", gopath)
+ return &app{
+ appFiles: appFiles,
+ imports: im,
+ }, err
+}
+
+// buildContext returns the context for building the source.
+func buildContext(tags []string) *build.Context {
+ return &build.Context{
+ GOARCH: "amd64",
+ GOOS: "linux",
+ GOROOT: build.Default.GOROOT,
+ GOPATH: build.Default.GOPATH,
+ Compiler: build.Default.Compiler,
+ BuildTags: append(build.Default.BuildTags, tags...),
+ }
+}
+
+// bundle bundles the app into a temporary directory.
+func (s *app) bundle() (tmpdir string, err error) {
+ workDir, err := ioutil.TempDir("", os.Args[0])
+ if err != nil {
+ return "", fmt.Errorf("unable to create tmpdir: %v", err)
+ }
+
+ for srcDir, importName := range s.imports {
+ dstDir := "_gopath/src/" + importName
+ if err := copyTree(workDir, dstDir, srcDir); err != nil {
+ return workDir, fmt.Errorf("unable to copy directory %v to %v: %v", srcDir, dstDir, err)
+ }
+ }
+ if err := copyTree(workDir, ".", "."); err != nil {
+ return workDir, fmt.Errorf("unable to copy root directory to /app: %v", err)
+ }
+ return workDir, nil
+}
+
+// imports returns a map of all import directories (recursively) used by the app.
+// The return value maps full directory names to original import names.
+func imports(ctxt *build.Context, srcDir string, gopath []string) (map[string]string, error) {
+ pkg, err := ctxt.ImportDir(srcDir, 0)
+ if err != nil {
+ return nil, err
+ }
+
+ // Resolve all non-standard-library imports
+ result := make(map[string]string)
+ for _, v := range pkg.Imports {
+ if !strings.Contains(v, ".") {
+ continue
+ }
+ src, err := findInGopath(v, gopath)
+ if err != nil {
+ return nil, fmt.Errorf("unable to find import %v in gopath %v: %v", v, gopath, err)
+ }
+ if _, ok := result[src]; ok { // Already processed
+ continue
+ }
+ result[src] = v
+ im, err := imports(ctxt, src, gopath)
+ if err != nil {
+ return nil, fmt.Errorf("unable to parse package %v: %v", src, err)
+ }
+ for k, v := range im {
+ result[k] = v
+ }
+ }
+ return result, nil
+}
+
+// findInGopath searches the gopath for the named import directory.
+func findInGopath(dir string, gopath []string) (string, error) {
+ if v, ok := gopathCache[dir]; ok {
+ return v, nil
+ }
+ for _, v := range gopath {
+ dst := filepath.Join(v, "src", dir)
+ if _, err := os.Stat(dst); err == nil {
+ gopathCache[dir] = dst
+ return dst, nil
+ }
+ }
+ return "", fmt.Errorf("unable to find package %v in gopath %v", dir, gopath)
+}
+
+// copyTree copies srcDir to dstDir relative to dstRoot, ignoring skipFiles.
+func copyTree(dstRoot, dstDir, srcDir string) error {
+ d := filepath.Join(dstRoot, dstDir)
+ if err := os.MkdirAll(d, 0755); err != nil {
+ return fmt.Errorf("unable to create directory %v: %v", d, err)
+ }
+
+ entries, err := ioutil.ReadDir(srcDir)
+ if err != nil {
+ return fmt.Errorf("unable to read dir %v: %v", srcDir, err)
+ }
+ for _, entry := range entries {
+ n := entry.Name()
+ if skipFiles[n] {
+ continue
+ }
+ s := filepath.Join(srcDir, n)
+ d := filepath.Join(dstDir, n)
+ if entry.IsDir() {
+ if err := copyTree(dstRoot, d, s); err != nil {
+ return fmt.Errorf("unable to copy dir %v to %v: %v", s, d, err)
+ }
+ continue
+ }
+ if err := copyFile(dstRoot, d, s); err != nil {
+ return fmt.Errorf("unable to copy dir %v to %v: %v", s, d, err)
+ }
+ }
+ return nil
+}
+
+// copyFile copies src to dst relative to dstRoot.
+func copyFile(dstRoot, dst, src string) error {
+ s, err := os.Open(src)
+ if err != nil {
+ return fmt.Errorf("unable to open %v: %v", src, err)
+ }
+ defer s.Close()
+
+ dst = filepath.Join(dstRoot, dst)
+ d, err := os.Create(dst)
+ if err != nil {
+ return fmt.Errorf("unable to create %v: %v", dst)
+ }
+ _, err = io.Copy(d, s)
+ if err != nil {
+ d.Close() // ignore error, copy already failed.
+ return fmt.Errorf("unable to copy %v to %v: %v", src, dst, err)
+ }
+ if err := d.Close(); err != nil {
+ return fmt.Errorf("unable to close %v: %v", dst, err)
+ }
+ return nil
+}
+
+// appFiles returns a list of all Go source files in the app.
+func appFiles(ctxt *build.Context) ([]string, error) {
+ pkg, err := ctxt.ImportDir(".", 0)
+ if err != nil {
+ return nil, err
+ }
+ if !pkg.IsCommand() {
+ return nil, fmt.Errorf(`the root of your app needs to be package "main" (currently %q). Please see https://cloud.google.com/appengine/docs/go/managed-vms for more details on structuring your app.`, pkg.Name)
+ }
+ var appFiles []string
+ for _, f := range pkg.GoFiles {
+ n := filepath.Join(".", f)
+ appFiles = append(appFiles, n)
+ }
+ return appFiles, nil
+}
diff --git a/go/src/google.golang.org/appengine/datastore/doc.go b/go/src/google.golang.org/appengine/datastore/doc.go
index 38164e9..8296c36 100644
--- a/go/src/google.golang.org/appengine/datastore/doc.go
+++ b/go/src/google.golang.org/appengine/datastore/doc.go
@@ -105,9 +105,9 @@
If the options is "" then the comma may be omitted. There are no other
recognized options.
-Fields (except for []byte) are indexed by default. Strings longer than 500
-characters cannot be indexed; fields used to store long strings should be
-tagged with "noindex". Similarly, ByteStrings longer than 500 bytes cannot be
+Fields (except for []byte) are indexed by default. Strings longer than 1500
+bytes cannot be indexed; fields used to store long strings should be
+tagged with "noindex". Similarly, ByteStrings longer than 1500 bytes cannot be
indexed.
Example code:
@@ -187,9 +187,9 @@
Sum int `datastore:"-"`
}
- func (x *CustomPropsExample) Load(c <-chan Property) error {
+ func (x *CustomPropsExample) Load(ps []datastore.Property) error {
// Load I and J as usual.
- if err := datastore.LoadStruct(x, c); err != nil {
+ if err := datastore.LoadStruct(x, ps); err != nil {
return err
}
// Derive the Sum field.
@@ -197,24 +197,24 @@
return nil
}
- func (x *CustomPropsExample) Save(c chan<- Property) error {
- defer close(c)
+ func (x *CustomPropsExample) Save() ([]datastore.Property, error) {
// Validate the Sum field.
if x.Sum != x.I + x.J {
return errors.New("CustomPropsExample has inconsistent sum")
}
// Save I and J as usual. The code below is equivalent to calling
- // "return datastore.SaveStruct(x, c)", but is done manually for
+ // "return datastore.SaveStruct(x)", but is done manually for
// demonstration purposes.
- c <- datastore.Property{
- Name: "I",
- Value: int64(x.I),
+ return []datastore.Property{
+ {
+ Name: "I",
+ Value: int64(x.I),
+ },
+ {
+ Name: "J",
+ Value: int64(x.J),
+ },
}
- c <- datastore.Property{
- Name: "J",
- Value: int64(x.J),
- }
- return nil
}
The *PropertyList type implements PropertyLoadSaver, and can therefore hold an
diff --git a/go/src/google.golang.org/appengine/datastore/prop.go b/go/src/google.golang.org/appengine/datastore/prop.go
index b0a1e0f..3caef9a 100644
--- a/go/src/google.golang.org/appengine/datastore/prop.go
+++ b/go/src/google.golang.org/appengine/datastore/prop.go
@@ -13,7 +13,7 @@
)
// Entities with more than this many indexed properties will not be saved.
-const maxIndexedProperties = 5000
+const maxIndexedProperties = 20000
// []byte fields more than 1 megabyte long will not be loaded or saved.
const maxBlobLen = 1 << 20
@@ -63,7 +63,7 @@
Multiple bool
}
-// ByteString is a short byte slice (up to 500 bytes) that can be indexed.
+// ByteString is a short byte slice (up to 1500 bytes) that can be indexed.
type ByteString []byte
// PropertyLoadSaver can be converted from and to a slice of Properties.
diff --git a/go/src/google.golang.org/appengine/datastore/transaction.go b/go/src/google.golang.org/appengine/datastore/transaction.go
index fa63f52..a7f3f2b 100644
--- a/go/src/google.golang.org/appengine/datastore/transaction.go
+++ b/go/src/google.golang.org/appengine/datastore/transaction.go
@@ -39,7 +39,8 @@
// returning nil if it succeeds. If the commit fails due to a conflicting
// transaction, RunInTransaction retries f, each time with a new transaction
// context. It gives up and returns ErrConcurrentTransaction after three
-// failed attempts.
+// failed attempts. The number of attempts can be configured by specifying
+// TransactionOptions.Attempts.
//
// If f returns non-nil, then any datastore changes will not be applied and
// RunInTransaction returns that same error. The function f is not retried.
@@ -48,13 +49,20 @@
// must be careful not to assume that any of f's changes have been committed
// until RunInTransaction returns nil.
//
+// Since f may be called multiple times, f should usually be idempotent.
+// datastore.Get is not idempotent when unmarshaling slice fields.
+//
// Nested transactions are not supported; c may not be a transaction context.
func RunInTransaction(c context.Context, f func(tc context.Context) error, opts *TransactionOptions) error {
xg := false
if opts != nil {
xg = opts.XG
}
- for i := 0; i < 3; i++ {
+ attempts := 3
+ if opts != nil && opts.Attempts > 0 {
+ attempts = opts.Attempts
+ }
+ for i := 0; i < attempts; i++ {
if err := internal.RunTransactionOnce(c, f, xg); err != internal.ErrConcurrentTransaction {
return err
}
@@ -73,4 +81,7 @@
// It is valid to set XG to true even if the transaction is within a
// single entity group.
XG bool
+ // Attempts controls the number of retries to perform when commits fail
+ // due to a conflicting transaction. If omitted, it defaults to 3.
+ Attempts int
}
diff --git a/go/src/google.golang.org/appengine/delay/delay.go b/go/src/google.golang.org/appengine/delay/delay.go
index 347b839..6d628fc 100644
--- a/go/src/google.golang.org/appengine/delay/delay.go
+++ b/go/src/google.golang.org/appengine/delay/delay.go
@@ -134,20 +134,17 @@
}
// Call invokes a delayed function.
-// f.Call(c, ...)
+// err := f.Call(c, ...)
// is equivalent to
// t, _ := f.Task(...)
-// taskqueue.Add(c, t, "")
-func (f *Function) Call(c context.Context, args ...interface{}) {
+// err := taskqueue.Add(c, t, "")
+func (f *Function) Call(c context.Context, args ...interface{}) error {
t, err := f.Task(args...)
if err != nil {
- log.Errorf(c, "%v", err)
- return
+ return err
}
- if _, err := taskqueueAdder(c, t, queue); err != nil {
- log.Errorf(c, "delay: taskqueue.Add failed: %v", err)
- return
- }
+ _, err = taskqueueAdder(c, t, queue)
+ return err
}
// Task creates a Task that will invoke the function.
diff --git a/go/src/google.golang.org/appengine/delay/delay_test.go b/go/src/google.golang.org/appengine/delay/delay_test.go
index 1a1c74c..ef68d20 100644
--- a/go/src/google.golang.org/appengine/delay/delay_test.go
+++ b/go/src/google.golang.org/appengine/delay/delay_test.go
@@ -109,13 +109,8 @@
func TestInvalidFunction(t *testing.T) {
c := newFakeContext()
- invalidFunc.Call(c.ctx)
-
- wantLogging := [][]interface{}{
- {"ERROR", "%v", fmt.Errorf("delay: func is invalid: %s", errFirstArg)},
- }
- if !reflect.DeepEqual(c.logging, wantLogging) {
- t.Errorf("Incorrect logging: got %+v, want %+v", c.logging, wantLogging)
+ if got, want := invalidFunc.Call(c.ctx), fmt.Errorf("delay: func is invalid: %s", errFirstArg); got.Error() != want.Error() {
+ t.Errorf("Incorrect error: got %q, want %q", got, want)
}
}
@@ -137,12 +132,8 @@
t.Errorf("Got %d calls to taskqueueAdder, want 3", calls)
}
- varFunc.Call(c.ctx, "%d %s", 12, "a string is bad")
- wantLogging := [][]interface{}{
- {"ERROR", "%v", errors.New("delay: argument 3 has wrong type: string is not assignable to int")},
- }
- if !reflect.DeepEqual(c.logging, wantLogging) {
- t.Errorf("Incorrect logging: got %+v, want %+v", c.logging, wantLogging)
+ if got, want := varFunc.Call(c.ctx, "%d %s", 12, "a string is bad"), errors.New("delay: argument 3 has wrong type: string is not assignable to int"); got.Error() != want.Error() {
+ t.Errorf("Incorrect error: got %q, want %q", got, want)
}
}
@@ -151,17 +142,28 @@
c := newFakeContext()
- regFunc.Call(c.ctx)
- regFunc.Call(c.ctx, "lala", 53)
- regFunc.Call(c.ctx, 53)
-
- wantLogging := [][]interface{}{
- {"ERROR", "%v", errors.New("delay: too few arguments to func: 1 < 2")},
- {"ERROR", "%v", errors.New("delay: too many arguments to func: 3 > 2")},
- {"ERROR", "%v", errors.New("delay: argument 1 has wrong type: int is not assignable to string")},
+ tests := []struct {
+ args []interface{} // all except context
+ wantErr string
+ }{
+ {
+ args: nil,
+ wantErr: "delay: too few arguments to func: 1 < 2",
+ },
+ {
+ args: []interface{}{"lala", 53},
+ wantErr: "delay: too many arguments to func: 3 > 2",
+ },
+ {
+ args: []interface{}{53},
+ wantErr: "delay: argument 1 has wrong type: int is not assignable to string",
+ },
}
- if !reflect.DeepEqual(c.logging, wantLogging) {
- t.Errorf("Incorrect logging: got %+v, want %+v", c.logging, wantLogging)
+ for i, tc := range tests {
+ got := regFunc.Call(c.ctx, tc.args...)
+ if got.Error() != tc.wantErr {
+ t.Errorf("Call %v: got %q, want %q", i, got, tc.wantErr)
+ }
}
}
diff --git a/go/src/google.golang.org/appengine/demos/guestbook/app.yaml b/go/src/google.golang.org/appengine/demos/guestbook/app.yaml
index 7837139..95c088f 100644
--- a/go/src/google.golang.org/appengine/demos/guestbook/app.yaml
+++ b/go/src/google.golang.org/appengine/demos/guestbook/app.yaml
@@ -1,6 +1,4 @@
# Demo application for Managed VMs.
-application: vm-guestbook
-version: 1
runtime: go
vm: true
api_version: go1
diff --git a/go/src/google.golang.org/appengine/demos/guestbook/guestbook.go b/go/src/google.golang.org/appengine/demos/guestbook/guestbook.go
index 9069484..0c6e897 100644
--- a/go/src/google.golang.org/appengine/demos/guestbook/guestbook.go
+++ b/go/src/google.golang.org/appengine/demos/guestbook/guestbook.go
@@ -2,7 +2,10 @@
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
-package guestbook
+// This example only works on Managed VMs.
+// +build !appengine
+
+package main
import (
"html/template"
@@ -25,15 +28,16 @@
Date time.Time
}
-func init() {
+func main() {
http.HandleFunc("/", handleMainPage)
http.HandleFunc("/sign", handleSign)
+ appengine.Main()
}
// guestbookKey returns the key used for all guestbook entries.
-func guestbookKey(c context.Context) *datastore.Key {
+func guestbookKey(ctx context.Context) *datastore.Key {
// The string "default_guestbook" here could be varied to have multiple guestbooks.
- return datastore.NewKey(c, "Guestbook", "default_guestbook", 0, nil)
+ return datastore.NewKey(ctx, "Guestbook", "default_guestbook", 0, nil)
}
var tpl = template.Must(template.ParseGlob("templates/*.html"))
@@ -48,24 +52,24 @@
return
}
- c := appengine.NewContext(r)
+ ctx := appengine.NewContext(r)
tic := time.Now()
- q := datastore.NewQuery("Greeting").Ancestor(guestbookKey(c)).Order("-Date").Limit(10)
+ q := datastore.NewQuery("Greeting").Ancestor(guestbookKey(ctx)).Order("-Date").Limit(10)
var gg []*Greeting
- if _, err := q.GetAll(c, &gg); err != nil {
+ if _, err := q.GetAll(ctx, &gg); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
- log.Errorf(c, "GetAll: %v", err)
+ log.Errorf(ctx, "GetAll: %v", err)
return
}
- log.Infof(c, "Datastore lookup took %s", time.Since(tic).String())
- log.Infof(c, "Rendering %d greetings", len(gg))
+ log.Infof(ctx, "Datastore lookup took %s", time.Since(tic).String())
+ log.Infof(ctx, "Rendering %d greetings", len(gg))
var email, logout, login string
- if u := user.Current(c); u != nil {
- logout, _ = user.LogoutURL(c, "/")
+ if u := user.Current(ctx); u != nil {
+ logout, _ = user.LogoutURL(ctx, "/")
email = u.Email
} else {
- login, _ = user.LoginURL(c, "/")
+ login, _ = user.LoginURL(ctx, "/")
}
data := struct {
Greetings []*Greeting
@@ -78,7 +82,7 @@
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := tpl.ExecuteTemplate(w, "guestbook.html", data); err != nil {
- log.Errorf(c, "%v", err)
+ log.Errorf(ctx, "%v", err)
}
}
@@ -87,16 +91,16 @@
http.Error(w, "POST requests only", http.StatusMethodNotAllowed)
return
}
- c := appengine.NewContext(r)
+ ctx := appengine.NewContext(r)
g := &Greeting{
Content: r.FormValue("content"),
Date: time.Now(),
}
- if u := user.Current(c); u != nil {
+ if u := user.Current(ctx); u != nil {
g.Author = u.String()
}
- key := datastore.NewIncompleteKey(c, "Greeting", guestbookKey(c))
- if _, err := datastore.Put(c, key, g); err != nil {
+ key := datastore.NewIncompleteKey(ctx, "Greeting", guestbookKey(ctx))
+ if _, err := datastore.Put(ctx, key, g); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
diff --git a/go/src/google.golang.org/appengine/demos/helloworld/app.yaml b/go/src/google.golang.org/appengine/demos/helloworld/app.yaml
index bac034b..e0b2eb7 100644
--- a/go/src/google.golang.org/appengine/demos/helloworld/app.yaml
+++ b/go/src/google.golang.org/appengine/demos/helloworld/app.yaml
@@ -1,5 +1,3 @@
-application: helloworld
-version: 1
runtime: go
api_version: go1
vm: true
diff --git a/go/src/google.golang.org/appengine/demos/helloworld/helloworld.go b/go/src/google.golang.org/appengine/demos/helloworld/helloworld.go
index 2324929..fcd9b0b 100644
--- a/go/src/google.golang.org/appengine/demos/helloworld/helloworld.go
+++ b/go/src/google.golang.org/appengine/demos/helloworld/helloworld.go
@@ -2,7 +2,10 @@
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
-package helloworld
+// This example only works on Managed VMs.
+// +build !appengine
+
+package main
import (
"html/template"
@@ -15,8 +18,9 @@
var initTime = time.Now()
-func init() {
+func main() {
http.HandleFunc("/", handle)
+ appengine.Main()
}
func handle(w http.ResponseWriter, r *http.Request) {
@@ -25,8 +29,8 @@
return
}
- c := appengine.NewContext(r)
- log.Infof(c, "Serving the front page.")
+ ctx := appengine.NewContext(r)
+ log.Infof(ctx, "Serving the front page.")
tmpl.Execute(w, time.Since(initTime))
}
diff --git a/go/src/google.golang.org/appengine/identity.go b/go/src/google.golang.org/appengine/identity.go
index 336e3ff..b8dcf8f 100644
--- a/go/src/google.golang.org/appengine/identity.go
+++ b/go/src/google.golang.org/appengine/identity.go
@@ -126,15 +126,14 @@
}
// SignBytes signs bytes using a private key unique to your application.
-func SignBytes(c context.Context, bytes []byte) (string, []byte, error) {
+func SignBytes(c context.Context, bytes []byte) (keyName string, signature []byte, err error) {
req := &pb.SignForAppRequest{BytesToSign: bytes}
res := &pb.SignForAppResponse{}
- err := internal.Call(c, "app_identity_service", "SignForApp", req, res)
- if err != nil {
+ if err := internal.Call(c, "app_identity_service", "SignForApp", req, res); err != nil {
return "", nil, err
}
- return res.GetKeyName(), res.GetSignatureBytes(), err
+ return res.GetKeyName(), res.GetSignatureBytes(), nil
}
func init() {
diff --git a/go/src/google.golang.org/appengine/internal/api.go b/go/src/google.golang.org/appengine/internal/api.go
index 40a6587..84b18a9 100644
--- a/go/src/google.golang.org/appengine/internal/api.go
+++ b/go/src/google.golang.org/appengine/internal/api.go
@@ -139,6 +139,7 @@
defer func() {
if x := recover(); x != nil {
logf(c, 4, "%s", renderPanic(x)) // 4 == critical
+ c.outCode = 500
}
}()
@@ -215,19 +216,26 @@
return c
}
-func toContext(c *context) netcontext.Context {
- ctx := netcontext.WithValue(netcontext.Background(), &contextKey, c)
+func withContext(parent netcontext.Context, c *context) netcontext.Context {
+ ctx := netcontext.WithValue(parent, &contextKey, c)
if ns := c.req.Header.Get(curNamespaceHeader); ns != "" {
ctx = WithNamespace(ctx, ns)
}
return ctx
}
-func IncomingHeaders(ctx netcontext.Context) http.Header {
- return fromContext(ctx).req.Header
+func toContext(c *context) netcontext.Context {
+ return withContext(netcontext.Background(), c)
}
-func NewContext(req *http.Request) netcontext.Context {
+func IncomingHeaders(ctx netcontext.Context) http.Header {
+ if c := fromContext(ctx); c != nil {
+ return c.req.Header
+ }
+ return nil
+}
+
+func WithContext(parent netcontext.Context, req *http.Request) netcontext.Context {
ctxs.Lock()
c := ctxs.m[req]
ctxs.Unlock()
@@ -238,7 +246,7 @@
// so that stack traces will be more sensible.
log.Panic("appengine: NewContext passed an unknown http.Request")
}
- return toContext(c)
+ return withContext(parent, c)
}
func BackgroundContext() netcontext.Context {
@@ -253,7 +261,7 @@
appID := partitionlessAppID()
escAppID := strings.Replace(strings.Replace(appID, ":", "_", -1), ".", "_", -1)
majVersion := VersionID(nil)
- if i := strings.Index(majVersion, "_"); i >= 0 {
+ if i := strings.Index(majVersion, "."); i > 0 {
majVersion = majVersion[:i]
}
ticket := fmt.Sprintf("%s/%s.%s.%s", escAppID, ModuleName(nil), majVersion, InstanceID())
@@ -377,7 +385,7 @@
}
func Call(ctx netcontext.Context, service, method string, in, out proto.Message) error {
- if f, ok := ctx.Value(&callOverrideKey).(callOverrideFunc); ok {
+ if f, ctx, ok := callOverrideFromContext(ctx); ok {
return f(ctx, service, method, in, out)
}
diff --git a/go/src/google.golang.org/appengine/internal/api_classic.go b/go/src/google.golang.org/appengine/internal/api_classic.go
index 32dbd3a..986191d 100644
--- a/go/src/google.golang.org/appengine/internal/api_classic.go
+++ b/go/src/google.golang.org/appengine/internal/api_classic.go
@@ -1,3 +1,7 @@
+// Copyright 2015 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
// +build appengine
package internal
@@ -27,8 +31,8 @@
return fromContext(ctx)
}
-func toContext(c appengine.Context) netcontext.Context {
- ctx := netcontext.WithValue(netcontext.Background(), &contextKey, c)
+func withContext(parent netcontext.Context, c appengine.Context) netcontext.Context {
+ ctx := netcontext.WithValue(parent, &contextKey, c)
s := &basepb.StringProto{}
c.Call("__go__", "GetNamespace", &basepb.VoidProto{}, s, nil)
@@ -39,13 +43,22 @@
return ctx
}
-func NewContext(req *http.Request) netcontext.Context {
+func IncomingHeaders(ctx netcontext.Context) http.Header {
+ if c := fromContext(ctx); c != nil {
+ if req, ok := c.Request().(*http.Request); ok {
+ return req.Header
+ }
+ }
+ return nil
+}
+
+func WithContext(parent netcontext.Context, req *http.Request) netcontext.Context {
c := appengine.NewContext(req)
- return toContext(c)
+ return withContext(parent, c)
}
func Call(ctx netcontext.Context, service, method string, in, out proto.Message) error {
- if f, ok := ctx.Value(&callOverrideKey).(callOverrideFunc); ok {
+ if f, ctx, ok := callOverrideFromContext(ctx); ok {
return f(ctx, service, method, in, out)
}
@@ -70,7 +83,22 @@
}
}
- return c.Call(service, method, in, out, opts)
+ err := c.Call(service, method, in, out, opts)
+ switch v := err.(type) {
+ case *appengine_internal.APIError:
+ return &APIError{
+ Service: v.Service,
+ Detail: v.Detail,
+ Code: v.Code,
+ }
+ case *appengine_internal.CallError:
+ return &CallError{
+ Detail: v.Detail,
+ Code: v.Code,
+ Timeout: v.Timeout,
+ }
+ }
+ return err
}
func handleHTTP(w http.ResponseWriter, r *http.Request) {
diff --git a/go/src/google.golang.org/appengine/internal/api_common.go b/go/src/google.golang.org/appengine/internal/api_common.go
index c499124..4f34b58 100644
--- a/go/src/google.golang.org/appengine/internal/api_common.go
+++ b/go/src/google.golang.org/appengine/internal/api_common.go
@@ -1,3 +1,7 @@
+// Copyright 2015 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
package internal
import (
@@ -7,10 +11,29 @@
type callOverrideFunc func(ctx netcontext.Context, service, method string, in, out proto.Message) error
-var callOverrideKey = "holds a callOverrideFunc"
+var callOverrideKey = "holds []callOverrideFunc"
func WithCallOverride(ctx netcontext.Context, f callOverrideFunc) netcontext.Context {
- return netcontext.WithValue(ctx, &callOverrideKey, f)
+ // We avoid appending to any existing call override
+ // so we don't risk overwriting a popped stack below.
+ var cofs []callOverrideFunc
+ if uf, ok := ctx.Value(&callOverrideKey).([]callOverrideFunc); ok {
+ cofs = append(cofs, uf...)
+ }
+ cofs = append(cofs, f)
+ return netcontext.WithValue(ctx, &callOverrideKey, cofs)
+}
+
+func callOverrideFromContext(ctx netcontext.Context) (callOverrideFunc, netcontext.Context, bool) {
+ cofs, _ := ctx.Value(&callOverrideKey).([]callOverrideFunc)
+ if len(cofs) == 0 {
+ return nil, nil, false
+ }
+ // We found a list of overrides; grab the last, and reconstitute a
+ // context that will hide it.
+ f := cofs[len(cofs)-1]
+ ctx = netcontext.WithValue(ctx, &callOverrideKey, cofs[:len(cofs)-1])
+ return f, ctx, true
}
type logOverrideFunc func(level int64, format string, args ...interface{})
diff --git a/go/src/google.golang.org/appengine/internal/api_race_test.go b/go/src/google.golang.org/appengine/internal/api_race_test.go
index d6977f1..6cfe906 100644
--- a/go/src/google.golang.org/appengine/internal/api_race_test.go
+++ b/go/src/google.golang.org/appengine/internal/api_race_test.go
@@ -1,3 +1,7 @@
+// Copyright 2014 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
// +build race
package internal
diff --git a/go/src/google.golang.org/appengine/internal/api_test.go b/go/src/google.golang.org/appengine/internal/api_test.go
index a8a6ed9..b090739 100644
--- a/go/src/google.golang.org/appengine/internal/api_test.go
+++ b/go/src/google.golang.org/appengine/internal/api_test.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
+// +build !appengine
+
package internal
import (
@@ -231,7 +233,7 @@
defer cleanup()
http.HandleFunc("/quick_log", func(w http.ResponseWriter, r *http.Request) {
- c := NewContext(r)
+ c := WithContext(netcontext.Background(), r)
Logf(c, 1, "It's a lovely day.")
w.WriteHeader(200)
w.Write(make([]byte, 100<<10)) // write 100 KB to force HTTP flush
@@ -307,6 +309,22 @@
}
}
+func TestPanickingHandler(t *testing.T) {
+ http.HandleFunc("/panic", func(http.ResponseWriter, *http.Request) {
+ panic("whoops!")
+ })
+ r := &http.Request{
+ Method: "GET",
+ URL: &url.URL{Scheme: "http", Path: "/panic"},
+ Body: ioutil.NopCloser(bytes.NewReader(nil)),
+ }
+ rec := httptest.NewRecorder()
+ handleHTTP(rec, r)
+ if rec.Code != 500 {
+ t.Errorf("Panicking handler returned HTTP %d, want HTTP %d", rec.Code, 500)
+ }
+}
+
var raceDetector = false
func TestAPICallAllocations(t *testing.T) {
@@ -342,7 +360,7 @@
}
// Lots of room for improvement...
- const min, max float64 = 80, 95
+ const min, max float64 = 75, 95
if avg < min || max < avg {
t.Errorf("Allocations per API call = %g, want in [%g,%g]", avg, min, max)
}
@@ -407,3 +425,34 @@
// Wait for stdin to be closed.
io.Copy(ioutil.Discard, os.Stdin)
}
+
+func TestBackgroundContext(t *testing.T) {
+ environ := []struct {
+ key, value string
+ }{
+ {"GAE_LONG_APP_ID", "my-app-id"},
+ {"GAE_MINOR_VERSION", "067924799508853122"},
+ {"GAE_MODULE_INSTANCE", "0"},
+ {"GAE_MODULE_NAME", "default"},
+ {"GAE_MODULE_VERSION", "20150612t184001"},
+ }
+ for _, v := range environ {
+ old := os.Getenv(v.key)
+ os.Setenv(v.key, v.value)
+ v.value = old
+ }
+ defer func() { // Restore old environment after the test completes.
+ for _, v := range environ {
+ if v.value == "" {
+ os.Unsetenv(v.key)
+ continue
+ }
+ os.Setenv(v.key, v.value)
+ }
+ }()
+
+ ctx, key := fromContext(BackgroundContext()), "X-Magic-Ticket-Header"
+ if g, w := ctx.req.Header.Get(key), "my-app-id/default.20150612t184001.0"; g != w {
+ t.Errorf("%v = %q, want %q", key, g, w)
+ }
+}
diff --git a/go/src/google.golang.org/appengine/internal/app_identity/app_identity_service.pb.go b/go/src/google.golang.org/appengine/internal/app_identity/app_identity_service.pb.go
index 532dad7..87d9701 100644
--- a/go/src/google.golang.org/appengine/internal/app_identity/app_identity_service.pb.go
+++ b/go/src/google.golang.org/appengine/internal/app_identity/app_identity_service.pb.go
@@ -25,10 +25,12 @@
package app_identity
import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
+var _ = fmt.Errorf
var _ = math.Inf
type AppIdentityServiceError_ErrorCode int32
diff --git a/go/src/google.golang.org/appengine/internal/base/api_base.pb.go b/go/src/google.golang.org/appengine/internal/base/api_base.pb.go
index 9ecc29b..36a1956 100644
--- a/go/src/google.golang.org/appengine/internal/base/api_base.pb.go
+++ b/go/src/google.golang.org/appengine/internal/base/api_base.pb.go
@@ -20,10 +20,12 @@
package base
import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
+var _ = fmt.Errorf
var _ = math.Inf
type StringProto struct {
@@ -129,6 +131,3 @@
func (m *VoidProto) Reset() { *m = VoidProto{} }
func (m *VoidProto) String() string { return proto.CompactTextString(m) }
func (*VoidProto) ProtoMessage() {}
-
-func init() {
-}
diff --git a/go/src/google.golang.org/appengine/internal/blobstore/blobstore_service.pb.go b/go/src/google.golang.org/appengine/internal/blobstore/blobstore_service.pb.go
new file mode 100644
index 0000000..8705ec3
--- /dev/null
+++ b/go/src/google.golang.org/appengine/internal/blobstore/blobstore_service.pb.go
@@ -0,0 +1,347 @@
+// Code generated by protoc-gen-go.
+// source: google.golang.org/appengine/internal/blobstore/blobstore_service.proto
+// DO NOT EDIT!
+
+/*
+Package blobstore is a generated protocol buffer package.
+
+It is generated from these files:
+ google.golang.org/appengine/internal/blobstore/blobstore_service.proto
+
+It has these top-level messages:
+ BlobstoreServiceError
+ CreateUploadURLRequest
+ CreateUploadURLResponse
+ DeleteBlobRequest
+ FetchDataRequest
+ FetchDataResponse
+ CloneBlobRequest
+ CloneBlobResponse
+ DecodeBlobKeyRequest
+ DecodeBlobKeyResponse
+ CreateEncodedGoogleStorageKeyRequest
+ CreateEncodedGoogleStorageKeyResponse
+*/
+package blobstore
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+type BlobstoreServiceError_ErrorCode int32
+
+const (
+ BlobstoreServiceError_OK BlobstoreServiceError_ErrorCode = 0
+ BlobstoreServiceError_INTERNAL_ERROR BlobstoreServiceError_ErrorCode = 1
+ BlobstoreServiceError_URL_TOO_LONG BlobstoreServiceError_ErrorCode = 2
+ BlobstoreServiceError_PERMISSION_DENIED BlobstoreServiceError_ErrorCode = 3
+ BlobstoreServiceError_BLOB_NOT_FOUND BlobstoreServiceError_ErrorCode = 4
+ BlobstoreServiceError_DATA_INDEX_OUT_OF_RANGE BlobstoreServiceError_ErrorCode = 5
+ BlobstoreServiceError_BLOB_FETCH_SIZE_TOO_LARGE BlobstoreServiceError_ErrorCode = 6
+ BlobstoreServiceError_ARGUMENT_OUT_OF_RANGE BlobstoreServiceError_ErrorCode = 8
+ BlobstoreServiceError_INVALID_BLOB_KEY BlobstoreServiceError_ErrorCode = 9
+)
+
+var BlobstoreServiceError_ErrorCode_name = map[int32]string{
+ 0: "OK",
+ 1: "INTERNAL_ERROR",
+ 2: "URL_TOO_LONG",
+ 3: "PERMISSION_DENIED",
+ 4: "BLOB_NOT_FOUND",
+ 5: "DATA_INDEX_OUT_OF_RANGE",
+ 6: "BLOB_FETCH_SIZE_TOO_LARGE",
+ 8: "ARGUMENT_OUT_OF_RANGE",
+ 9: "INVALID_BLOB_KEY",
+}
+var BlobstoreServiceError_ErrorCode_value = map[string]int32{
+ "OK": 0,
+ "INTERNAL_ERROR": 1,
+ "URL_TOO_LONG": 2,
+ "PERMISSION_DENIED": 3,
+ "BLOB_NOT_FOUND": 4,
+ "DATA_INDEX_OUT_OF_RANGE": 5,
+ "BLOB_FETCH_SIZE_TOO_LARGE": 6,
+ "ARGUMENT_OUT_OF_RANGE": 8,
+ "INVALID_BLOB_KEY": 9,
+}
+
+func (x BlobstoreServiceError_ErrorCode) Enum() *BlobstoreServiceError_ErrorCode {
+ p := new(BlobstoreServiceError_ErrorCode)
+ *p = x
+ return p
+}
+func (x BlobstoreServiceError_ErrorCode) String() string {
+ return proto.EnumName(BlobstoreServiceError_ErrorCode_name, int32(x))
+}
+func (x *BlobstoreServiceError_ErrorCode) UnmarshalJSON(data []byte) error {
+ value, err := proto.UnmarshalJSONEnum(BlobstoreServiceError_ErrorCode_value, data, "BlobstoreServiceError_ErrorCode")
+ if err != nil {
+ return err
+ }
+ *x = BlobstoreServiceError_ErrorCode(value)
+ return nil
+}
+
+type BlobstoreServiceError struct {
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *BlobstoreServiceError) Reset() { *m = BlobstoreServiceError{} }
+func (m *BlobstoreServiceError) String() string { return proto.CompactTextString(m) }
+func (*BlobstoreServiceError) ProtoMessage() {}
+
+type CreateUploadURLRequest struct {
+ SuccessPath *string `protobuf:"bytes,1,req,name=success_path" json:"success_path,omitempty"`
+ MaxUploadSizeBytes *int64 `protobuf:"varint,2,opt,name=max_upload_size_bytes" json:"max_upload_size_bytes,omitempty"`
+ MaxUploadSizePerBlobBytes *int64 `protobuf:"varint,3,opt,name=max_upload_size_per_blob_bytes" json:"max_upload_size_per_blob_bytes,omitempty"`
+ GsBucketName *string `protobuf:"bytes,4,opt,name=gs_bucket_name" json:"gs_bucket_name,omitempty"`
+ UrlExpiryTimeSeconds *int32 `protobuf:"varint,5,opt,name=url_expiry_time_seconds" json:"url_expiry_time_seconds,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *CreateUploadURLRequest) Reset() { *m = CreateUploadURLRequest{} }
+func (m *CreateUploadURLRequest) String() string { return proto.CompactTextString(m) }
+func (*CreateUploadURLRequest) ProtoMessage() {}
+
+func (m *CreateUploadURLRequest) GetSuccessPath() string {
+ if m != nil && m.SuccessPath != nil {
+ return *m.SuccessPath
+ }
+ return ""
+}
+
+func (m *CreateUploadURLRequest) GetMaxUploadSizeBytes() int64 {
+ if m != nil && m.MaxUploadSizeBytes != nil {
+ return *m.MaxUploadSizeBytes
+ }
+ return 0
+}
+
+func (m *CreateUploadURLRequest) GetMaxUploadSizePerBlobBytes() int64 {
+ if m != nil && m.MaxUploadSizePerBlobBytes != nil {
+ return *m.MaxUploadSizePerBlobBytes
+ }
+ return 0
+}
+
+func (m *CreateUploadURLRequest) GetGsBucketName() string {
+ if m != nil && m.GsBucketName != nil {
+ return *m.GsBucketName
+ }
+ return ""
+}
+
+func (m *CreateUploadURLRequest) GetUrlExpiryTimeSeconds() int32 {
+ if m != nil && m.UrlExpiryTimeSeconds != nil {
+ return *m.UrlExpiryTimeSeconds
+ }
+ return 0
+}
+
+type CreateUploadURLResponse struct {
+ Url *string `protobuf:"bytes,1,req,name=url" json:"url,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *CreateUploadURLResponse) Reset() { *m = CreateUploadURLResponse{} }
+func (m *CreateUploadURLResponse) String() string { return proto.CompactTextString(m) }
+func (*CreateUploadURLResponse) ProtoMessage() {}
+
+func (m *CreateUploadURLResponse) GetUrl() string {
+ if m != nil && m.Url != nil {
+ return *m.Url
+ }
+ return ""
+}
+
+type DeleteBlobRequest struct {
+ BlobKey []string `protobuf:"bytes,1,rep,name=blob_key" json:"blob_key,omitempty"`
+ Token *string `protobuf:"bytes,2,opt,name=token" json:"token,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *DeleteBlobRequest) Reset() { *m = DeleteBlobRequest{} }
+func (m *DeleteBlobRequest) String() string { return proto.CompactTextString(m) }
+func (*DeleteBlobRequest) ProtoMessage() {}
+
+func (m *DeleteBlobRequest) GetBlobKey() []string {
+ if m != nil {
+ return m.BlobKey
+ }
+ return nil
+}
+
+func (m *DeleteBlobRequest) GetToken() string {
+ if m != nil && m.Token != nil {
+ return *m.Token
+ }
+ return ""
+}
+
+type FetchDataRequest struct {
+ BlobKey *string `protobuf:"bytes,1,req,name=blob_key" json:"blob_key,omitempty"`
+ StartIndex *int64 `protobuf:"varint,2,req,name=start_index" json:"start_index,omitempty"`
+ EndIndex *int64 `protobuf:"varint,3,req,name=end_index" json:"end_index,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *FetchDataRequest) Reset() { *m = FetchDataRequest{} }
+func (m *FetchDataRequest) String() string { return proto.CompactTextString(m) }
+func (*FetchDataRequest) ProtoMessage() {}
+
+func (m *FetchDataRequest) GetBlobKey() string {
+ if m != nil && m.BlobKey != nil {
+ return *m.BlobKey
+ }
+ return ""
+}
+
+func (m *FetchDataRequest) GetStartIndex() int64 {
+ if m != nil && m.StartIndex != nil {
+ return *m.StartIndex
+ }
+ return 0
+}
+
+func (m *FetchDataRequest) GetEndIndex() int64 {
+ if m != nil && m.EndIndex != nil {
+ return *m.EndIndex
+ }
+ return 0
+}
+
+type FetchDataResponse struct {
+ Data []byte `protobuf:"bytes,1000,req,name=data" json:"data,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *FetchDataResponse) Reset() { *m = FetchDataResponse{} }
+func (m *FetchDataResponse) String() string { return proto.CompactTextString(m) }
+func (*FetchDataResponse) ProtoMessage() {}
+
+func (m *FetchDataResponse) GetData() []byte {
+ if m != nil {
+ return m.Data
+ }
+ return nil
+}
+
+type CloneBlobRequest struct {
+ BlobKey []byte `protobuf:"bytes,1,req,name=blob_key" json:"blob_key,omitempty"`
+ MimeType []byte `protobuf:"bytes,2,req,name=mime_type" json:"mime_type,omitempty"`
+ TargetAppId []byte `protobuf:"bytes,3,req,name=target_app_id" json:"target_app_id,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *CloneBlobRequest) Reset() { *m = CloneBlobRequest{} }
+func (m *CloneBlobRequest) String() string { return proto.CompactTextString(m) }
+func (*CloneBlobRequest) ProtoMessage() {}
+
+func (m *CloneBlobRequest) GetBlobKey() []byte {
+ if m != nil {
+ return m.BlobKey
+ }
+ return nil
+}
+
+func (m *CloneBlobRequest) GetMimeType() []byte {
+ if m != nil {
+ return m.MimeType
+ }
+ return nil
+}
+
+func (m *CloneBlobRequest) GetTargetAppId() []byte {
+ if m != nil {
+ return m.TargetAppId
+ }
+ return nil
+}
+
+type CloneBlobResponse struct {
+ BlobKey []byte `protobuf:"bytes,1,req,name=blob_key" json:"blob_key,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *CloneBlobResponse) Reset() { *m = CloneBlobResponse{} }
+func (m *CloneBlobResponse) String() string { return proto.CompactTextString(m) }
+func (*CloneBlobResponse) ProtoMessage() {}
+
+func (m *CloneBlobResponse) GetBlobKey() []byte {
+ if m != nil {
+ return m.BlobKey
+ }
+ return nil
+}
+
+type DecodeBlobKeyRequest struct {
+ BlobKey []string `protobuf:"bytes,1,rep,name=blob_key" json:"blob_key,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *DecodeBlobKeyRequest) Reset() { *m = DecodeBlobKeyRequest{} }
+func (m *DecodeBlobKeyRequest) String() string { return proto.CompactTextString(m) }
+func (*DecodeBlobKeyRequest) ProtoMessage() {}
+
+func (m *DecodeBlobKeyRequest) GetBlobKey() []string {
+ if m != nil {
+ return m.BlobKey
+ }
+ return nil
+}
+
+type DecodeBlobKeyResponse struct {
+ Decoded []string `protobuf:"bytes,1,rep,name=decoded" json:"decoded,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *DecodeBlobKeyResponse) Reset() { *m = DecodeBlobKeyResponse{} }
+func (m *DecodeBlobKeyResponse) String() string { return proto.CompactTextString(m) }
+func (*DecodeBlobKeyResponse) ProtoMessage() {}
+
+func (m *DecodeBlobKeyResponse) GetDecoded() []string {
+ if m != nil {
+ return m.Decoded
+ }
+ return nil
+}
+
+type CreateEncodedGoogleStorageKeyRequest struct {
+ Filename *string `protobuf:"bytes,1,req,name=filename" json:"filename,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *CreateEncodedGoogleStorageKeyRequest) Reset() { *m = CreateEncodedGoogleStorageKeyRequest{} }
+func (m *CreateEncodedGoogleStorageKeyRequest) String() string { return proto.CompactTextString(m) }
+func (*CreateEncodedGoogleStorageKeyRequest) ProtoMessage() {}
+
+func (m *CreateEncodedGoogleStorageKeyRequest) GetFilename() string {
+ if m != nil && m.Filename != nil {
+ return *m.Filename
+ }
+ return ""
+}
+
+type CreateEncodedGoogleStorageKeyResponse struct {
+ BlobKey *string `protobuf:"bytes,1,req,name=blob_key" json:"blob_key,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *CreateEncodedGoogleStorageKeyResponse) Reset() { *m = CreateEncodedGoogleStorageKeyResponse{} }
+func (m *CreateEncodedGoogleStorageKeyResponse) String() string { return proto.CompactTextString(m) }
+func (*CreateEncodedGoogleStorageKeyResponse) ProtoMessage() {}
+
+func (m *CreateEncodedGoogleStorageKeyResponse) GetBlobKey() string {
+ if m != nil && m.BlobKey != nil {
+ return *m.BlobKey
+ }
+ return ""
+}
+
+func init() {
+}
diff --git a/go/src/google.golang.org/appengine/internal/blobstore/blobstore_service.proto b/go/src/google.golang.org/appengine/internal/blobstore/blobstore_service.proto
new file mode 100644
index 0000000..33b2650
--- /dev/null
+++ b/go/src/google.golang.org/appengine/internal/blobstore/blobstore_service.proto
@@ -0,0 +1,71 @@
+syntax = "proto2";
+option go_package = "blobstore";
+
+package appengine;
+
+message BlobstoreServiceError {
+ enum ErrorCode {
+ OK = 0;
+ INTERNAL_ERROR = 1;
+ URL_TOO_LONG = 2;
+ PERMISSION_DENIED = 3;
+ BLOB_NOT_FOUND = 4;
+ DATA_INDEX_OUT_OF_RANGE = 5;
+ BLOB_FETCH_SIZE_TOO_LARGE = 6;
+ ARGUMENT_OUT_OF_RANGE = 8;
+ INVALID_BLOB_KEY = 9;
+ }
+}
+
+message CreateUploadURLRequest {
+ required string success_path = 1;
+ optional int64 max_upload_size_bytes = 2;
+ optional int64 max_upload_size_per_blob_bytes = 3;
+ optional string gs_bucket_name = 4;
+ optional int32 url_expiry_time_seconds = 5;
+}
+
+message CreateUploadURLResponse {
+ required string url = 1;
+}
+
+message DeleteBlobRequest {
+ repeated string blob_key = 1;
+ optional string token = 2;
+}
+
+message FetchDataRequest {
+ required string blob_key = 1;
+ required int64 start_index = 2;
+ required int64 end_index = 3;
+}
+
+message FetchDataResponse {
+ required bytes data = 1000 [ctype = CORD];
+}
+
+message CloneBlobRequest {
+ required bytes blob_key = 1;
+ required bytes mime_type = 2;
+ required bytes target_app_id = 3;
+}
+
+message CloneBlobResponse {
+ required bytes blob_key = 1;
+}
+
+message DecodeBlobKeyRequest {
+ repeated string blob_key = 1;
+}
+
+message DecodeBlobKeyResponse {
+ repeated string decoded = 1;
+}
+
+message CreateEncodedGoogleStorageKeyRequest {
+ required string filename = 1;
+}
+
+message CreateEncodedGoogleStorageKeyResponse {
+ required string blob_key = 1;
+}
diff --git a/go/src/google.golang.org/appengine/internal/channel/channel_service.pb.go b/go/src/google.golang.org/appengine/internal/channel/channel_service.pb.go
index f2334eb..7b8d00c 100644
--- a/go/src/google.golang.org/appengine/internal/channel/channel_service.pb.go
+++ b/go/src/google.golang.org/appengine/internal/channel/channel_service.pb.go
@@ -17,10 +17,12 @@
package channel
import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
+var _ = fmt.Errorf
var _ = math.Inf
type ChannelServiceError_ErrorCode int32
diff --git a/go/src/google.golang.org/appengine/internal/datastore/datastore_v3.pb.go b/go/src/google.golang.org/appengine/internal/datastore/datastore_v3.pb.go
index cd78da9..5c64e0f 100644
--- a/go/src/google.golang.org/appengine/internal/datastore/datastore_v3.pb.go
+++ b/go/src/google.golang.org/appengine/internal/datastore/datastore_v3.pb.go
@@ -51,10 +51,12 @@
package datastore
import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
+var _ = fmt.Errorf
var _ = math.Inf
type Property_Meaning int32
diff --git a/go/src/google.golang.org/appengine/internal/identity_classic.go b/go/src/google.golang.org/appengine/internal/identity_classic.go
index 08e060d..e6b9227 100644
--- a/go/src/google.golang.org/appengine/internal/identity_classic.go
+++ b/go/src/google.golang.org/appengine/internal/identity_classic.go
@@ -1,3 +1,7 @@
+// Copyright 2015 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
// +build appengine
package internal
@@ -18,5 +22,6 @@
func ModuleName(ctx netcontext.Context) string { return appengine.ModuleName(fromContext(ctx)) }
func VersionID(ctx netcontext.Context) string { return appengine.VersionID(fromContext(ctx)) }
func InstanceID() string { return appengine.InstanceID() }
+func IsDevAppServer() bool { return appengine.IsDevAppServer() }
func fullyQualifiedAppID(ctx netcontext.Context) string { return fromContext(ctx).FullyQualifiedAppID() }
diff --git a/go/src/google.golang.org/appengine/internal/identity_vm.go b/go/src/google.golang.org/appengine/internal/identity_vm.go
index a1dfff9..f011197 100644
--- a/go/src/google.golang.org/appengine/internal/identity_vm.go
+++ b/go/src/google.golang.org/appengine/internal/identity_vm.go
@@ -56,10 +56,10 @@
}
func VersionID(_ netcontext.Context) string {
- if s := os.Getenv("GAE_MODULE_VERSION"); s != "" {
- return s
+ if s1, s2 := os.Getenv("GAE_MODULE_VERSION"), os.Getenv("GAE_MINOR_VERSION"); s1 != "" && s2 != "" {
+ return s1 + "." + s2
}
- return string(mustGetMetadata("instance/attributes/gae_backend_version"))
+ return string(mustGetMetadata("instance/attributes/gae_backend_version")) + "." + string(mustGetMetadata("instance/attributes/gae_minor_version"))
}
func InstanceID() string {
@@ -91,3 +91,7 @@
}
return appID
}
+
+func IsDevAppServer() bool {
+ return os.Getenv("RUN_WITH_DEVAPPSERVER") != ""
+}
diff --git a/go/src/google.golang.org/appengine/internal/image/images_service.pb.go b/go/src/google.golang.org/appengine/internal/image/images_service.pb.go
index 27cf17d..ba7c722 100644
--- a/go/src/google.golang.org/appengine/internal/image/images_service.pb.go
+++ b/go/src/google.golang.org/appengine/internal/image/images_service.pb.go
@@ -32,10 +32,12 @@
package image
import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
+var _ = fmt.Errorf
var _ = math.Inf
type ImagesServiceError_ErrorCode int32
diff --git a/go/src/google.golang.org/appengine/internal/internal.go b/go/src/google.golang.org/appengine/internal/internal.go
index 8b3135b..914145b 100644
--- a/go/src/google.golang.org/appengine/internal/internal.go
+++ b/go/src/google.golang.org/appengine/internal/internal.go
@@ -109,11 +109,6 @@
return e.Timeout
}
-// The comment below must not be changed.
-// It is used by go-app-builder to recognise that this package has
-// the internal.Main function to use in the synthetic main.
-// The gophers party all night; the rabbits provide the beats.
-
// Main is designed so that the complete generated main package is:
//
// package main
diff --git a/go/src/google.golang.org/appengine/internal/internal_test.go b/go/src/google.golang.org/appengine/internal/internal_test.go
index 226028d..dc684f5 100644
--- a/go/src/google.golang.org/appengine/internal/internal_test.go
+++ b/go/src/google.golang.org/appengine/internal/internal_test.go
@@ -1,3 +1,7 @@
+// Copyright 2014 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
package internal
import (
diff --git a/go/src/google.golang.org/appengine/internal/log/log_service.pb.go b/go/src/google.golang.org/appengine/internal/log/log_service.pb.go
index ebb2bab..20c595b 100644
--- a/go/src/google.golang.org/appengine/internal/log/log_service.pb.go
+++ b/go/src/google.golang.org/appengine/internal/log/log_service.pb.go
@@ -27,10 +27,12 @@
package log
import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
+var _ = fmt.Errorf
var _ = math.Inf
type LogServiceError_ErrorCode int32
diff --git a/go/src/google.golang.org/appengine/internal/mail/mail_service.pb.go b/go/src/google.golang.org/appengine/internal/mail/mail_service.pb.go
index 06a5d51..a6978c3 100644
--- a/go/src/google.golang.org/appengine/internal/mail/mail_service.pb.go
+++ b/go/src/google.golang.org/appengine/internal/mail/mail_service.pb.go
@@ -17,10 +17,12 @@
package mail
import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
+var _ = fmt.Errorf
var _ = math.Inf
type MailServiceError_ErrorCode int32
diff --git a/go/src/google.golang.org/appengine/internal/memcache/memcache_service.pb.go b/go/src/google.golang.org/appengine/internal/memcache/memcache_service.pb.go
index d0d1f5f..b1eca0f 100644
--- a/go/src/google.golang.org/appengine/internal/memcache/memcache_service.pb.go
+++ b/go/src/google.golang.org/appengine/internal/memcache/memcache_service.pb.go
@@ -32,10 +32,12 @@
package memcache
import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
+var _ = fmt.Errorf
var _ = math.Inf
type MemcacheServiceError_ErrorCode int32
diff --git a/go/src/google.golang.org/appengine/internal/modules/modules_service.pb.go b/go/src/google.golang.org/appengine/internal/modules/modules_service.pb.go
index 341e7d1..a0145ed 100644
--- a/go/src/google.golang.org/appengine/internal/modules/modules_service.pb.go
+++ b/go/src/google.golang.org/appengine/internal/modules/modules_service.pb.go
@@ -30,10 +30,12 @@
package modules
import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
+var _ = fmt.Errorf
var _ = math.Inf
type ModulesServiceError_ErrorCode int32
diff --git a/go/src/google.golang.org/appengine/internal/net.go b/go/src/google.golang.org/appengine/internal/net.go
index 12ddfbf..3b94cf0 100644
--- a/go/src/google.golang.org/appengine/internal/net.go
+++ b/go/src/google.golang.org/appengine/internal/net.go
@@ -43,21 +43,14 @@
}
type limitConn struct {
- mu sync.Mutex // only for closing the net.Conn
+ close sync.Once
net.Conn
}
func (lc *limitConn) Close() error {
- lc.mu.Lock()
- defer lc.mu.Unlock()
-
- if lc.Conn == nil {
- // Silently ignore double close.
- return nil
- }
- limitRelease()
- err := lc.Conn.Close()
- lc.Conn = nil
- runtime.SetFinalizer(lc, nil)
- return err
+ defer lc.close.Do(func() {
+ limitRelease()
+ runtime.SetFinalizer(lc, nil)
+ })
+ return lc.Conn.Close()
}
diff --git a/go/src/google.golang.org/appengine/internal/net_test.go b/go/src/google.golang.org/appengine/internal/net_test.go
index 3c0c152..24da8bb 100644
--- a/go/src/google.golang.org/appengine/internal/net_test.go
+++ b/go/src/google.golang.org/appengine/internal/net_test.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
+// +build !appengine
+
package internal
import (
diff --git a/go/src/google.golang.org/appengine/internal/regen.sh b/go/src/google.golang.org/appengine/internal/regen.sh
index 51c6178..f98e761 100755
--- a/go/src/google.golang.org/appengine/internal/regen.sh
+++ b/go/src/google.golang.org/appengine/internal/regen.sh
@@ -29,11 +29,6 @@
done
for f in $(find $PKG/internal -name '*.pb.go'); do
- # Fix up import lines.
- # This should be fixed upstream.
- # https://code.google.com/p/goprotobuf/issues/detail?id=32
- sed -i '/^import.*\.pb"$/s,/[^/]*\.pb"$,",' $f
-
# Remove proto.RegisterEnum calls.
# These cause duplicate registration panics when these packages
# are used on classic App Engine. proto.RegisterEnum only affects
diff --git a/go/src/google.golang.org/appengine/internal/remote_api/remote_api.pb.go b/go/src/google.golang.org/appengine/internal/remote_api/remote_api.pb.go
index aceca21..526bd39 100644
--- a/go/src/google.golang.org/appengine/internal/remote_api/remote_api.pb.go
+++ b/go/src/google.golang.org/appengine/internal/remote_api/remote_api.pb.go
@@ -17,10 +17,12 @@
package remote_api
import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
+var _ = fmt.Errorf
var _ = math.Inf
type RpcError_ErrorCode int32
diff --git a/go/src/google.golang.org/appengine/internal/search/search.pb.go b/go/src/google.golang.org/appengine/internal/search/search.pb.go
index 16cd51e..10d2398 100644
--- a/go/src/google.golang.org/appengine/internal/search/search.pb.go
+++ b/go/src/google.golang.org/appengine/internal/search/search.pb.go
@@ -15,8 +15,10 @@
FieldValue
Field
FieldTypes
+ IndexShardSettings
FacetValue
Facet
+ DocumentMetadata
Document
SearchServiceError
RequestStatus
@@ -44,7 +46,7 @@
FacetRequestParam
FacetAutoDetectParam
FacetRequest
- FacetRefine
+ FacetRefinement
SearchParams
SearchRequest
FacetResultValue
@@ -55,10 +57,12 @@
package search
import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
+var _ = fmt.Errorf
var _ = math.Inf
type Scope_Type int32
@@ -194,18 +198,15 @@
const (
FacetValue_ATOM FacetValue_ContentType = 2
- FacetValue_DATE FacetValue_ContentType = 3
FacetValue_NUMBER FacetValue_ContentType = 4
)
var FacetValue_ContentType_name = map[int32]string{
2: "ATOM",
- 3: "DATE",
4: "NUMBER",
}
var FacetValue_ContentType_value = map[string]int32{
"ATOM": 2,
- "DATE": 3,
"NUMBER": 4,
}
@@ -736,6 +737,48 @@
return nil
}
+type IndexShardSettings struct {
+ PrevNumShards []int32 `protobuf:"varint,1,rep,name=prev_num_shards" json:"prev_num_shards,omitempty"`
+ NumShards *int32 `protobuf:"varint,2,req,name=num_shards,def=1" json:"num_shards,omitempty"`
+ PrevNumShardsSearchFalse []int32 `protobuf:"varint,3,rep,name=prev_num_shards_search_false" json:"prev_num_shards_search_false,omitempty"`
+ LocalReplica *string `protobuf:"bytes,4,opt,name=local_replica,def=" json:"local_replica,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *IndexShardSettings) Reset() { *m = IndexShardSettings{} }
+func (m *IndexShardSettings) String() string { return proto.CompactTextString(m) }
+func (*IndexShardSettings) ProtoMessage() {}
+
+const Default_IndexShardSettings_NumShards int32 = 1
+
+func (m *IndexShardSettings) GetPrevNumShards() []int32 {
+ if m != nil {
+ return m.PrevNumShards
+ }
+ return nil
+}
+
+func (m *IndexShardSettings) GetNumShards() int32 {
+ if m != nil && m.NumShards != nil {
+ return *m.NumShards
+ }
+ return Default_IndexShardSettings_NumShards
+}
+
+func (m *IndexShardSettings) GetPrevNumShardsSearchFalse() []int32 {
+ if m != nil {
+ return m.PrevNumShardsSearchFalse
+ }
+ return nil
+}
+
+func (m *IndexShardSettings) GetLocalReplica() string {
+ if m != nil && m.LocalReplica != nil {
+ return *m.LocalReplica
+ }
+ return ""
+}
+
type FacetValue struct {
Type *FacetValue_ContentType `protobuf:"varint,1,opt,name=type,enum=search.FacetValue_ContentType,def=2" json:"type,omitempty"`
StringValue *string `protobuf:"bytes,3,opt,name=string_value" json:"string_value,omitempty"`
@@ -786,16 +829,38 @@
return nil
}
+type DocumentMetadata struct {
+ Version *int64 `protobuf:"varint,1,opt,name=version" json:"version,omitempty"`
+ CommittedStVersion *int64 `protobuf:"varint,2,opt,name=committed_st_version" json:"committed_st_version,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *DocumentMetadata) Reset() { *m = DocumentMetadata{} }
+func (m *DocumentMetadata) String() string { return proto.CompactTextString(m) }
+func (*DocumentMetadata) ProtoMessage() {}
+
+func (m *DocumentMetadata) GetVersion() int64 {
+ if m != nil && m.Version != nil {
+ return *m.Version
+ }
+ return 0
+}
+
+func (m *DocumentMetadata) GetCommittedStVersion() int64 {
+ if m != nil && m.CommittedStVersion != nil {
+ return *m.CommittedStVersion
+ }
+ return 0
+}
+
type Document struct {
- Id *string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
- Language *string `protobuf:"bytes,2,opt,name=language,def=en" json:"language,omitempty"`
- Field []*Field `protobuf:"bytes,3,rep,name=field" json:"field,omitempty"`
- OrderId *int32 `protobuf:"varint,4,opt,name=order_id" json:"order_id,omitempty"`
- Storage *Document_Storage `protobuf:"varint,5,opt,name=storage,enum=search.Document_Storage,def=0" json:"storage,omitempty"`
- Acl *AccessControlList `protobuf:"bytes,6,opt,name=acl" json:"acl,omitempty"`
- Version *int64 `protobuf:"varint,7,opt,name=version" json:"version,omitempty"`
- Facet []*Facet `protobuf:"bytes,8,rep,name=facet" json:"facet,omitempty"`
- XXX_unrecognized []byte `json:"-"`
+ Id *string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
+ Language *string `protobuf:"bytes,2,opt,name=language,def=en" json:"language,omitempty"`
+ Field []*Field `protobuf:"bytes,3,rep,name=field" json:"field,omitempty"`
+ OrderId *int32 `protobuf:"varint,4,opt,name=order_id" json:"order_id,omitempty"`
+ Storage *Document_Storage `protobuf:"varint,5,opt,name=storage,enum=search.Document_Storage,def=0" json:"storage,omitempty"`
+ Facet []*Facet `protobuf:"bytes,8,rep,name=facet" json:"facet,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (m *Document) Reset() { *m = Document{} }
@@ -840,20 +905,6 @@
return Default_Document_Storage
}
-func (m *Document) GetAcl() *AccessControlList {
- if m != nil {
- return m.Acl
- }
- return nil
-}
-
-func (m *Document) GetVersion() int64 {
- if m != nil && m.Version != nil {
- return *m.Version
- }
- return 0
-}
-
func (m *Document) GetFacet() []*Facet {
if m != nil {
return m.Facet
@@ -872,6 +923,7 @@
type RequestStatus struct {
Code *SearchServiceError_ErrorCode `protobuf:"varint,1,req,name=code,enum=search.SearchServiceError_ErrorCode" json:"code,omitempty"`
ErrorDetail *string `protobuf:"bytes,2,opt,name=error_detail" json:"error_detail,omitempty"`
+ CanonicalCode *int32 `protobuf:"varint,3,opt,name=canonical_code" json:"canonical_code,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
@@ -893,6 +945,13 @@
return ""
}
+func (m *RequestStatus) GetCanonicalCode() int32 {
+ if m != nil && m.CanonicalCode != nil {
+ return *m.CanonicalCode
+ }
+ return 0
+}
+
type IndexSpec struct {
Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
Consistency *IndexSpec_Consistency `protobuf:"varint,2,opt,name=consistency,enum=search.IndexSpec_Consistency,def=1" json:"consistency,omitempty"`
@@ -1652,10 +1711,9 @@
}
type FacetRequest struct {
- Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
- Type *FacetValue_ContentType `protobuf:"varint,2,req,name=type,enum=search.FacetValue_ContentType" json:"type,omitempty"`
- Params *FacetRequestParam `protobuf:"bytes,3,opt,name=params" json:"params,omitempty"`
- XXX_unrecognized []byte `json:"-"`
+ Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
+ Params *FacetRequestParam `protobuf:"bytes,2,opt,name=params" json:"params,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (m *FacetRequest) Reset() { *m = FacetRequest{} }
@@ -1669,13 +1727,6 @@
return ""
}
-func (m *FacetRequest) GetType() FacetValue_ContentType {
- if m != nil && m.Type != nil {
- return *m.Type
- }
- return FacetValue_ATOM
-}
-
func (m *FacetRequest) GetParams() *FacetRequestParam {
if m != nil {
return m.Params
@@ -1683,48 +1734,56 @@
return nil
}
-type FacetRefine struct {
- Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
- Type *FacetValue_ContentType `protobuf:"varint,2,req,name=type,enum=search.FacetValue_ContentType" json:"type,omitempty"`
- Value *string `protobuf:"bytes,3,opt,name=value" json:"value,omitempty"`
- Start *string `protobuf:"bytes,4,opt,name=start" json:"start,omitempty"`
- End *string `protobuf:"bytes,5,opt,name=end" json:"end,omitempty"`
- XXX_unrecognized []byte `json:"-"`
+type FacetRefinement struct {
+ Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
+ Value *string `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"`
+ Range *FacetRefinement_Range `protobuf:"bytes,3,opt,name=range" json:"range,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
-func (m *FacetRefine) Reset() { *m = FacetRefine{} }
-func (m *FacetRefine) String() string { return proto.CompactTextString(m) }
-func (*FacetRefine) ProtoMessage() {}
+func (m *FacetRefinement) Reset() { *m = FacetRefinement{} }
+func (m *FacetRefinement) String() string { return proto.CompactTextString(m) }
+func (*FacetRefinement) ProtoMessage() {}
-func (m *FacetRefine) GetName() string {
+func (m *FacetRefinement) GetName() string {
if m != nil && m.Name != nil {
return *m.Name
}
return ""
}
-func (m *FacetRefine) GetType() FacetValue_ContentType {
- if m != nil && m.Type != nil {
- return *m.Type
- }
- return FacetValue_ATOM
-}
-
-func (m *FacetRefine) GetValue() string {
+func (m *FacetRefinement) GetValue() string {
if m != nil && m.Value != nil {
return *m.Value
}
return ""
}
-func (m *FacetRefine) GetStart() string {
+func (m *FacetRefinement) GetRange() *FacetRefinement_Range {
+ if m != nil {
+ return m.Range
+ }
+ return nil
+}
+
+type FacetRefinement_Range struct {
+ Start *string `protobuf:"bytes,1,opt,name=start" json:"start,omitempty"`
+ End *string `protobuf:"bytes,2,opt,name=end" json:"end,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *FacetRefinement_Range) Reset() { *m = FacetRefinement_Range{} }
+func (m *FacetRefinement_Range) String() string { return proto.CompactTextString(m) }
+func (*FacetRefinement_Range) ProtoMessage() {}
+
+func (m *FacetRefinement_Range) GetStart() string {
if m != nil && m.Start != nil {
return *m.Start
}
return ""
}
-func (m *FacetRefine) GetEnd() string {
+func (m *FacetRefinement_Range) GetEnd() string {
if m != nil && m.End != nil {
return *m.End
}
@@ -1746,8 +1805,9 @@
ParsingMode *SearchParams_ParsingMode `protobuf:"varint,13,opt,name=parsing_mode,enum=search.SearchParams_ParsingMode,def=0" json:"parsing_mode,omitempty"`
AutoDiscoverFacetCount *int32 `protobuf:"varint,15,opt,name=auto_discover_facet_count,def=0" json:"auto_discover_facet_count,omitempty"`
IncludeFacet []*FacetRequest `protobuf:"bytes,16,rep,name=include_facet" json:"include_facet,omitempty"`
- FacetRefine []*FacetRefine `protobuf:"bytes,17,rep,name=facet_refine" json:"facet_refine,omitempty"`
+ FacetRefinement []*FacetRefinement `protobuf:"bytes,17,rep,name=facet_refinement" json:"facet_refinement,omitempty"`
FacetAutoDetectParam *FacetAutoDetectParam `protobuf:"bytes,18,opt,name=facet_auto_detect_param" json:"facet_auto_detect_param,omitempty"`
+ FacetDepth *int32 `protobuf:"varint,19,opt,name=facet_depth,def=1000" json:"facet_depth,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
@@ -1759,6 +1819,7 @@
const Default_SearchParams_Limit int32 = 20
const Default_SearchParams_ParsingMode SearchParams_ParsingMode = SearchParams_STRICT
const Default_SearchParams_AutoDiscoverFacetCount int32 = 0
+const Default_SearchParams_FacetDepth int32 = 1000
func (m *SearchParams) GetIndexSpec() *IndexSpec {
if m != nil {
@@ -1858,9 +1919,9 @@
return nil
}
-func (m *SearchParams) GetFacetRefine() []*FacetRefine {
+func (m *SearchParams) GetFacetRefinement() []*FacetRefinement {
if m != nil {
- return m.FacetRefine
+ return m.FacetRefinement
}
return nil
}
@@ -1872,6 +1933,13 @@
return nil
}
+func (m *SearchParams) GetFacetDepth() int32 {
+ if m != nil && m.FacetDepth != nil {
+ return *m.FacetDepth
+ }
+ return Default_SearchParams_FacetDepth
+}
+
type SearchRequest struct {
Params *SearchParams `protobuf:"bytes,1,req,name=params" json:"params,omitempty"`
AppId []byte `protobuf:"bytes,3,opt,name=app_id" json:"app_id,omitempty"`
@@ -1897,9 +1965,10 @@
}
type FacetResultValue struct {
- Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
- Count *int32 `protobuf:"varint,2,req,name=count" json:"count,omitempty"`
- XXX_unrecognized []byte `json:"-"`
+ Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
+ Count *int32 `protobuf:"varint,2,req,name=count" json:"count,omitempty"`
+ Refinement *FacetRefinement `protobuf:"bytes,3,req,name=refinement" json:"refinement,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (m *FacetResultValue) Reset() { *m = FacetResultValue{} }
@@ -1920,11 +1989,17 @@
return 0
}
+func (m *FacetResultValue) GetRefinement() *FacetRefinement {
+ if m != nil {
+ return m.Refinement
+ }
+ return nil
+}
+
type FacetResult struct {
- Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
- Type *FacetValue_ContentType `protobuf:"varint,2,req,name=type,enum=search.FacetValue_ContentType" json:"type,omitempty"`
- Value []*FacetResultValue `protobuf:"bytes,3,rep,name=value" json:"value,omitempty"`
- XXX_unrecognized []byte `json:"-"`
+ Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
+ Value []*FacetResultValue `protobuf:"bytes,2,rep,name=value" json:"value,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
}
func (m *FacetResult) Reset() { *m = FacetResult{} }
@@ -1938,13 +2013,6 @@
return ""
}
-func (m *FacetResult) GetType() FacetValue_ContentType {
- if m != nil && m.Type != nil {
- return *m.Type
- }
- return FacetValue_ATOM
-}
-
func (m *FacetResult) GetValue() []*FacetResultValue {
if m != nil {
return m.Value
diff --git a/go/src/google.golang.org/appengine/internal/search/search.proto b/go/src/google.golang.org/appengine/internal/search/search.proto
index 6299841..219f4c3 100644
--- a/go/src/google.golang.org/appengine/internal/search/search.proto
+++ b/go/src/google.golang.org/appengine/internal/search/search.proto
@@ -67,10 +67,16 @@
repeated FieldValue.ContentType type = 2;
}
+message IndexShardSettings {
+ repeated int32 prev_num_shards = 1;
+ required int32 num_shards = 2 [default=1];
+ repeated int32 prev_num_shards_search_false = 3;
+ optional string local_replica = 4 [default = ""];
+}
+
message FacetValue {
enum ContentType {
ATOM = 2;
- DATE = 3;
NUMBER = 4;
}
@@ -83,6 +89,10 @@
required FacetValue value = 2;
}
+message DocumentMetadata {
+ optional int64 version = 1;
+ optional int64 committed_st_version = 2;
+}
message Document {
optional string id = 1;
@@ -95,8 +105,6 @@
}
optional Storage storage = 5 [default = DISK];
- optional AccessControlList acl = 6;
- optional int64 version = 7;
repeated Facet facet = 8;
}
@@ -115,6 +123,7 @@
message RequestStatus {
required SearchServiceError.ErrorCode code = 1;
optional string error_detail = 2;
+ optional int32 canonical_code = 3;
}
message IndexSpec {
@@ -297,16 +306,18 @@
message FacetRequest {
required string name = 1;
- required FacetValue.ContentType type = 2;
- optional FacetRequestParam params = 3;
+ optional FacetRequestParam params = 2;
}
-message FacetRefine {
+message FacetRefinement {
required string name = 1;
- required FacetValue.ContentType type = 2;
- optional string value = 3;
- optional string start = 4;
- optional string end = 5;
+ optional string value = 2;
+
+ message Range {
+ optional string start = 1;
+ optional string end = 2;
+ }
+ optional Range range = 3;
}
message SearchParams {
@@ -337,8 +348,9 @@
optional int32 auto_discover_facet_count = 15 [default = 0];
repeated FacetRequest include_facet = 16;
- repeated FacetRefine facet_refine = 17;
+ repeated FacetRefinement facet_refinement = 17;
optional FacetAutoDetectParam facet_auto_detect_param = 18;
+ optional int32 facet_depth = 19 [default=1000];
}
message SearchRequest {
@@ -350,12 +362,12 @@
message FacetResultValue {
required string name = 1;
required int32 count = 2;
+ required FacetRefinement refinement = 3;
}
message FacetResult {
required string name = 1;
- required FacetValue.ContentType type = 2;
- repeated FacetResultValue value = 3;
+ repeated FacetResultValue value = 2;
}
message SearchResult {
diff --git a/go/src/google.golang.org/appengine/internal/socket/socket_service.pb.go b/go/src/google.golang.org/appengine/internal/socket/socket_service.pb.go
new file mode 100644
index 0000000..60628ec
--- /dev/null
+++ b/go/src/google.golang.org/appengine/internal/socket/socket_service.pb.go
@@ -0,0 +1,1858 @@
+// Code generated by protoc-gen-go.
+// source: google.golang.org/appengine/internal/socket/socket_service.proto
+// DO NOT EDIT!
+
+/*
+Package socket is a generated protocol buffer package.
+
+It is generated from these files:
+ google.golang.org/appengine/internal/socket/socket_service.proto
+
+It has these top-level messages:
+ RemoteSocketServiceError
+ AddressPort
+ CreateSocketRequest
+ CreateSocketReply
+ BindRequest
+ BindReply
+ GetSocketNameRequest
+ GetSocketNameReply
+ GetPeerNameRequest
+ GetPeerNameReply
+ SocketOption
+ SetSocketOptionsRequest
+ SetSocketOptionsReply
+ GetSocketOptionsRequest
+ GetSocketOptionsReply
+ ConnectRequest
+ ConnectReply
+ ListenRequest
+ ListenReply
+ AcceptRequest
+ AcceptReply
+ ShutDownRequest
+ ShutDownReply
+ CloseRequest
+ CloseReply
+ SendRequest
+ SendReply
+ ReceiveRequest
+ ReceiveReply
+ PollEvent
+ PollRequest
+ PollReply
+ ResolveRequest
+ ResolveReply
+*/
+package socket
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+type RemoteSocketServiceError_ErrorCode int32
+
+const (
+ RemoteSocketServiceError_SYSTEM_ERROR RemoteSocketServiceError_ErrorCode = 1
+ RemoteSocketServiceError_GAI_ERROR RemoteSocketServiceError_ErrorCode = 2
+ RemoteSocketServiceError_FAILURE RemoteSocketServiceError_ErrorCode = 4
+ RemoteSocketServiceError_PERMISSION_DENIED RemoteSocketServiceError_ErrorCode = 5
+ RemoteSocketServiceError_INVALID_REQUEST RemoteSocketServiceError_ErrorCode = 6
+ RemoteSocketServiceError_SOCKET_CLOSED RemoteSocketServiceError_ErrorCode = 7
+)
+
+var RemoteSocketServiceError_ErrorCode_name = map[int32]string{
+ 1: "SYSTEM_ERROR",
+ 2: "GAI_ERROR",
+ 4: "FAILURE",
+ 5: "PERMISSION_DENIED",
+ 6: "INVALID_REQUEST",
+ 7: "SOCKET_CLOSED",
+}
+var RemoteSocketServiceError_ErrorCode_value = map[string]int32{
+ "SYSTEM_ERROR": 1,
+ "GAI_ERROR": 2,
+ "FAILURE": 4,
+ "PERMISSION_DENIED": 5,
+ "INVALID_REQUEST": 6,
+ "SOCKET_CLOSED": 7,
+}
+
+func (x RemoteSocketServiceError_ErrorCode) Enum() *RemoteSocketServiceError_ErrorCode {
+ p := new(RemoteSocketServiceError_ErrorCode)
+ *p = x
+ return p
+}
+func (x RemoteSocketServiceError_ErrorCode) String() string {
+ return proto.EnumName(RemoteSocketServiceError_ErrorCode_name, int32(x))
+}
+func (x *RemoteSocketServiceError_ErrorCode) UnmarshalJSON(data []byte) error {
+ value, err := proto.UnmarshalJSONEnum(RemoteSocketServiceError_ErrorCode_value, data, "RemoteSocketServiceError_ErrorCode")
+ if err != nil {
+ return err
+ }
+ *x = RemoteSocketServiceError_ErrorCode(value)
+ return nil
+}
+
+type RemoteSocketServiceError_SystemError int32
+
+const (
+ RemoteSocketServiceError_SYS_SUCCESS RemoteSocketServiceError_SystemError = 0
+ RemoteSocketServiceError_SYS_EPERM RemoteSocketServiceError_SystemError = 1
+ RemoteSocketServiceError_SYS_ENOENT RemoteSocketServiceError_SystemError = 2
+ RemoteSocketServiceError_SYS_ESRCH RemoteSocketServiceError_SystemError = 3
+ RemoteSocketServiceError_SYS_EINTR RemoteSocketServiceError_SystemError = 4
+ RemoteSocketServiceError_SYS_EIO RemoteSocketServiceError_SystemError = 5
+ RemoteSocketServiceError_SYS_ENXIO RemoteSocketServiceError_SystemError = 6
+ RemoteSocketServiceError_SYS_E2BIG RemoteSocketServiceError_SystemError = 7
+ RemoteSocketServiceError_SYS_ENOEXEC RemoteSocketServiceError_SystemError = 8
+ RemoteSocketServiceError_SYS_EBADF RemoteSocketServiceError_SystemError = 9
+ RemoteSocketServiceError_SYS_ECHILD RemoteSocketServiceError_SystemError = 10
+ RemoteSocketServiceError_SYS_EAGAIN RemoteSocketServiceError_SystemError = 11
+ RemoteSocketServiceError_SYS_EWOULDBLOCK RemoteSocketServiceError_SystemError = 11
+ RemoteSocketServiceError_SYS_ENOMEM RemoteSocketServiceError_SystemError = 12
+ RemoteSocketServiceError_SYS_EACCES RemoteSocketServiceError_SystemError = 13
+ RemoteSocketServiceError_SYS_EFAULT RemoteSocketServiceError_SystemError = 14
+ RemoteSocketServiceError_SYS_ENOTBLK RemoteSocketServiceError_SystemError = 15
+ RemoteSocketServiceError_SYS_EBUSY RemoteSocketServiceError_SystemError = 16
+ RemoteSocketServiceError_SYS_EEXIST RemoteSocketServiceError_SystemError = 17
+ RemoteSocketServiceError_SYS_EXDEV RemoteSocketServiceError_SystemError = 18
+ RemoteSocketServiceError_SYS_ENODEV RemoteSocketServiceError_SystemError = 19
+ RemoteSocketServiceError_SYS_ENOTDIR RemoteSocketServiceError_SystemError = 20
+ RemoteSocketServiceError_SYS_EISDIR RemoteSocketServiceError_SystemError = 21
+ RemoteSocketServiceError_SYS_EINVAL RemoteSocketServiceError_SystemError = 22
+ RemoteSocketServiceError_SYS_ENFILE RemoteSocketServiceError_SystemError = 23
+ RemoteSocketServiceError_SYS_EMFILE RemoteSocketServiceError_SystemError = 24
+ RemoteSocketServiceError_SYS_ENOTTY RemoteSocketServiceError_SystemError = 25
+ RemoteSocketServiceError_SYS_ETXTBSY RemoteSocketServiceError_SystemError = 26
+ RemoteSocketServiceError_SYS_EFBIG RemoteSocketServiceError_SystemError = 27
+ RemoteSocketServiceError_SYS_ENOSPC RemoteSocketServiceError_SystemError = 28
+ RemoteSocketServiceError_SYS_ESPIPE RemoteSocketServiceError_SystemError = 29
+ RemoteSocketServiceError_SYS_EROFS RemoteSocketServiceError_SystemError = 30
+ RemoteSocketServiceError_SYS_EMLINK RemoteSocketServiceError_SystemError = 31
+ RemoteSocketServiceError_SYS_EPIPE RemoteSocketServiceError_SystemError = 32
+ RemoteSocketServiceError_SYS_EDOM RemoteSocketServiceError_SystemError = 33
+ RemoteSocketServiceError_SYS_ERANGE RemoteSocketServiceError_SystemError = 34
+ RemoteSocketServiceError_SYS_EDEADLK RemoteSocketServiceError_SystemError = 35
+ RemoteSocketServiceError_SYS_EDEADLOCK RemoteSocketServiceError_SystemError = 35
+ RemoteSocketServiceError_SYS_ENAMETOOLONG RemoteSocketServiceError_SystemError = 36
+ RemoteSocketServiceError_SYS_ENOLCK RemoteSocketServiceError_SystemError = 37
+ RemoteSocketServiceError_SYS_ENOSYS RemoteSocketServiceError_SystemError = 38
+ RemoteSocketServiceError_SYS_ENOTEMPTY RemoteSocketServiceError_SystemError = 39
+ RemoteSocketServiceError_SYS_ELOOP RemoteSocketServiceError_SystemError = 40
+ RemoteSocketServiceError_SYS_ENOMSG RemoteSocketServiceError_SystemError = 42
+ RemoteSocketServiceError_SYS_EIDRM RemoteSocketServiceError_SystemError = 43
+ RemoteSocketServiceError_SYS_ECHRNG RemoteSocketServiceError_SystemError = 44
+ RemoteSocketServiceError_SYS_EL2NSYNC RemoteSocketServiceError_SystemError = 45
+ RemoteSocketServiceError_SYS_EL3HLT RemoteSocketServiceError_SystemError = 46
+ RemoteSocketServiceError_SYS_EL3RST RemoteSocketServiceError_SystemError = 47
+ RemoteSocketServiceError_SYS_ELNRNG RemoteSocketServiceError_SystemError = 48
+ RemoteSocketServiceError_SYS_EUNATCH RemoteSocketServiceError_SystemError = 49
+ RemoteSocketServiceError_SYS_ENOCSI RemoteSocketServiceError_SystemError = 50
+ RemoteSocketServiceError_SYS_EL2HLT RemoteSocketServiceError_SystemError = 51
+ RemoteSocketServiceError_SYS_EBADE RemoteSocketServiceError_SystemError = 52
+ RemoteSocketServiceError_SYS_EBADR RemoteSocketServiceError_SystemError = 53
+ RemoteSocketServiceError_SYS_EXFULL RemoteSocketServiceError_SystemError = 54
+ RemoteSocketServiceError_SYS_ENOANO RemoteSocketServiceError_SystemError = 55
+ RemoteSocketServiceError_SYS_EBADRQC RemoteSocketServiceError_SystemError = 56
+ RemoteSocketServiceError_SYS_EBADSLT RemoteSocketServiceError_SystemError = 57
+ RemoteSocketServiceError_SYS_EBFONT RemoteSocketServiceError_SystemError = 59
+ RemoteSocketServiceError_SYS_ENOSTR RemoteSocketServiceError_SystemError = 60
+ RemoteSocketServiceError_SYS_ENODATA RemoteSocketServiceError_SystemError = 61
+ RemoteSocketServiceError_SYS_ETIME RemoteSocketServiceError_SystemError = 62
+ RemoteSocketServiceError_SYS_ENOSR RemoteSocketServiceError_SystemError = 63
+ RemoteSocketServiceError_SYS_ENONET RemoteSocketServiceError_SystemError = 64
+ RemoteSocketServiceError_SYS_ENOPKG RemoteSocketServiceError_SystemError = 65
+ RemoteSocketServiceError_SYS_EREMOTE RemoteSocketServiceError_SystemError = 66
+ RemoteSocketServiceError_SYS_ENOLINK RemoteSocketServiceError_SystemError = 67
+ RemoteSocketServiceError_SYS_EADV RemoteSocketServiceError_SystemError = 68
+ RemoteSocketServiceError_SYS_ESRMNT RemoteSocketServiceError_SystemError = 69
+ RemoteSocketServiceError_SYS_ECOMM RemoteSocketServiceError_SystemError = 70
+ RemoteSocketServiceError_SYS_EPROTO RemoteSocketServiceError_SystemError = 71
+ RemoteSocketServiceError_SYS_EMULTIHOP RemoteSocketServiceError_SystemError = 72
+ RemoteSocketServiceError_SYS_EDOTDOT RemoteSocketServiceError_SystemError = 73
+ RemoteSocketServiceError_SYS_EBADMSG RemoteSocketServiceError_SystemError = 74
+ RemoteSocketServiceError_SYS_EOVERFLOW RemoteSocketServiceError_SystemError = 75
+ RemoteSocketServiceError_SYS_ENOTUNIQ RemoteSocketServiceError_SystemError = 76
+ RemoteSocketServiceError_SYS_EBADFD RemoteSocketServiceError_SystemError = 77
+ RemoteSocketServiceError_SYS_EREMCHG RemoteSocketServiceError_SystemError = 78
+ RemoteSocketServiceError_SYS_ELIBACC RemoteSocketServiceError_SystemError = 79
+ RemoteSocketServiceError_SYS_ELIBBAD RemoteSocketServiceError_SystemError = 80
+ RemoteSocketServiceError_SYS_ELIBSCN RemoteSocketServiceError_SystemError = 81
+ RemoteSocketServiceError_SYS_ELIBMAX RemoteSocketServiceError_SystemError = 82
+ RemoteSocketServiceError_SYS_ELIBEXEC RemoteSocketServiceError_SystemError = 83
+ RemoteSocketServiceError_SYS_EILSEQ RemoteSocketServiceError_SystemError = 84
+ RemoteSocketServiceError_SYS_ERESTART RemoteSocketServiceError_SystemError = 85
+ RemoteSocketServiceError_SYS_ESTRPIPE RemoteSocketServiceError_SystemError = 86
+ RemoteSocketServiceError_SYS_EUSERS RemoteSocketServiceError_SystemError = 87
+ RemoteSocketServiceError_SYS_ENOTSOCK RemoteSocketServiceError_SystemError = 88
+ RemoteSocketServiceError_SYS_EDESTADDRREQ RemoteSocketServiceError_SystemError = 89
+ RemoteSocketServiceError_SYS_EMSGSIZE RemoteSocketServiceError_SystemError = 90
+ RemoteSocketServiceError_SYS_EPROTOTYPE RemoteSocketServiceError_SystemError = 91
+ RemoteSocketServiceError_SYS_ENOPROTOOPT RemoteSocketServiceError_SystemError = 92
+ RemoteSocketServiceError_SYS_EPROTONOSUPPORT RemoteSocketServiceError_SystemError = 93
+ RemoteSocketServiceError_SYS_ESOCKTNOSUPPORT RemoteSocketServiceError_SystemError = 94
+ RemoteSocketServiceError_SYS_EOPNOTSUPP RemoteSocketServiceError_SystemError = 95
+ RemoteSocketServiceError_SYS_ENOTSUP RemoteSocketServiceError_SystemError = 95
+ RemoteSocketServiceError_SYS_EPFNOSUPPORT RemoteSocketServiceError_SystemError = 96
+ RemoteSocketServiceError_SYS_EAFNOSUPPORT RemoteSocketServiceError_SystemError = 97
+ RemoteSocketServiceError_SYS_EADDRINUSE RemoteSocketServiceError_SystemError = 98
+ RemoteSocketServiceError_SYS_EADDRNOTAVAIL RemoteSocketServiceError_SystemError = 99
+ RemoteSocketServiceError_SYS_ENETDOWN RemoteSocketServiceError_SystemError = 100
+ RemoteSocketServiceError_SYS_ENETUNREACH RemoteSocketServiceError_SystemError = 101
+ RemoteSocketServiceError_SYS_ENETRESET RemoteSocketServiceError_SystemError = 102
+ RemoteSocketServiceError_SYS_ECONNABORTED RemoteSocketServiceError_SystemError = 103
+ RemoteSocketServiceError_SYS_ECONNRESET RemoteSocketServiceError_SystemError = 104
+ RemoteSocketServiceError_SYS_ENOBUFS RemoteSocketServiceError_SystemError = 105
+ RemoteSocketServiceError_SYS_EISCONN RemoteSocketServiceError_SystemError = 106
+ RemoteSocketServiceError_SYS_ENOTCONN RemoteSocketServiceError_SystemError = 107
+ RemoteSocketServiceError_SYS_ESHUTDOWN RemoteSocketServiceError_SystemError = 108
+ RemoteSocketServiceError_SYS_ETOOMANYREFS RemoteSocketServiceError_SystemError = 109
+ RemoteSocketServiceError_SYS_ETIMEDOUT RemoteSocketServiceError_SystemError = 110
+ RemoteSocketServiceError_SYS_ECONNREFUSED RemoteSocketServiceError_SystemError = 111
+ RemoteSocketServiceError_SYS_EHOSTDOWN RemoteSocketServiceError_SystemError = 112
+ RemoteSocketServiceError_SYS_EHOSTUNREACH RemoteSocketServiceError_SystemError = 113
+ RemoteSocketServiceError_SYS_EALREADY RemoteSocketServiceError_SystemError = 114
+ RemoteSocketServiceError_SYS_EINPROGRESS RemoteSocketServiceError_SystemError = 115
+ RemoteSocketServiceError_SYS_ESTALE RemoteSocketServiceError_SystemError = 116
+ RemoteSocketServiceError_SYS_EUCLEAN RemoteSocketServiceError_SystemError = 117
+ RemoteSocketServiceError_SYS_ENOTNAM RemoteSocketServiceError_SystemError = 118
+ RemoteSocketServiceError_SYS_ENAVAIL RemoteSocketServiceError_SystemError = 119
+ RemoteSocketServiceError_SYS_EISNAM RemoteSocketServiceError_SystemError = 120
+ RemoteSocketServiceError_SYS_EREMOTEIO RemoteSocketServiceError_SystemError = 121
+ RemoteSocketServiceError_SYS_EDQUOT RemoteSocketServiceError_SystemError = 122
+ RemoteSocketServiceError_SYS_ENOMEDIUM RemoteSocketServiceError_SystemError = 123
+ RemoteSocketServiceError_SYS_EMEDIUMTYPE RemoteSocketServiceError_SystemError = 124
+ RemoteSocketServiceError_SYS_ECANCELED RemoteSocketServiceError_SystemError = 125
+ RemoteSocketServiceError_SYS_ENOKEY RemoteSocketServiceError_SystemError = 126
+ RemoteSocketServiceError_SYS_EKEYEXPIRED RemoteSocketServiceError_SystemError = 127
+ RemoteSocketServiceError_SYS_EKEYREVOKED RemoteSocketServiceError_SystemError = 128
+ RemoteSocketServiceError_SYS_EKEYREJECTED RemoteSocketServiceError_SystemError = 129
+ RemoteSocketServiceError_SYS_EOWNERDEAD RemoteSocketServiceError_SystemError = 130
+ RemoteSocketServiceError_SYS_ENOTRECOVERABLE RemoteSocketServiceError_SystemError = 131
+ RemoteSocketServiceError_SYS_ERFKILL RemoteSocketServiceError_SystemError = 132
+)
+
+var RemoteSocketServiceError_SystemError_name = map[int32]string{
+ 0: "SYS_SUCCESS",
+ 1: "SYS_EPERM",
+ 2: "SYS_ENOENT",
+ 3: "SYS_ESRCH",
+ 4: "SYS_EINTR",
+ 5: "SYS_EIO",
+ 6: "SYS_ENXIO",
+ 7: "SYS_E2BIG",
+ 8: "SYS_ENOEXEC",
+ 9: "SYS_EBADF",
+ 10: "SYS_ECHILD",
+ 11: "SYS_EAGAIN",
+ // Duplicate value: 11: "SYS_EWOULDBLOCK",
+ 12: "SYS_ENOMEM",
+ 13: "SYS_EACCES",
+ 14: "SYS_EFAULT",
+ 15: "SYS_ENOTBLK",
+ 16: "SYS_EBUSY",
+ 17: "SYS_EEXIST",
+ 18: "SYS_EXDEV",
+ 19: "SYS_ENODEV",
+ 20: "SYS_ENOTDIR",
+ 21: "SYS_EISDIR",
+ 22: "SYS_EINVAL",
+ 23: "SYS_ENFILE",
+ 24: "SYS_EMFILE",
+ 25: "SYS_ENOTTY",
+ 26: "SYS_ETXTBSY",
+ 27: "SYS_EFBIG",
+ 28: "SYS_ENOSPC",
+ 29: "SYS_ESPIPE",
+ 30: "SYS_EROFS",
+ 31: "SYS_EMLINK",
+ 32: "SYS_EPIPE",
+ 33: "SYS_EDOM",
+ 34: "SYS_ERANGE",
+ 35: "SYS_EDEADLK",
+ // Duplicate value: 35: "SYS_EDEADLOCK",
+ 36: "SYS_ENAMETOOLONG",
+ 37: "SYS_ENOLCK",
+ 38: "SYS_ENOSYS",
+ 39: "SYS_ENOTEMPTY",
+ 40: "SYS_ELOOP",
+ 42: "SYS_ENOMSG",
+ 43: "SYS_EIDRM",
+ 44: "SYS_ECHRNG",
+ 45: "SYS_EL2NSYNC",
+ 46: "SYS_EL3HLT",
+ 47: "SYS_EL3RST",
+ 48: "SYS_ELNRNG",
+ 49: "SYS_EUNATCH",
+ 50: "SYS_ENOCSI",
+ 51: "SYS_EL2HLT",
+ 52: "SYS_EBADE",
+ 53: "SYS_EBADR",
+ 54: "SYS_EXFULL",
+ 55: "SYS_ENOANO",
+ 56: "SYS_EBADRQC",
+ 57: "SYS_EBADSLT",
+ 59: "SYS_EBFONT",
+ 60: "SYS_ENOSTR",
+ 61: "SYS_ENODATA",
+ 62: "SYS_ETIME",
+ 63: "SYS_ENOSR",
+ 64: "SYS_ENONET",
+ 65: "SYS_ENOPKG",
+ 66: "SYS_EREMOTE",
+ 67: "SYS_ENOLINK",
+ 68: "SYS_EADV",
+ 69: "SYS_ESRMNT",
+ 70: "SYS_ECOMM",
+ 71: "SYS_EPROTO",
+ 72: "SYS_EMULTIHOP",
+ 73: "SYS_EDOTDOT",
+ 74: "SYS_EBADMSG",
+ 75: "SYS_EOVERFLOW",
+ 76: "SYS_ENOTUNIQ",
+ 77: "SYS_EBADFD",
+ 78: "SYS_EREMCHG",
+ 79: "SYS_ELIBACC",
+ 80: "SYS_ELIBBAD",
+ 81: "SYS_ELIBSCN",
+ 82: "SYS_ELIBMAX",
+ 83: "SYS_ELIBEXEC",
+ 84: "SYS_EILSEQ",
+ 85: "SYS_ERESTART",
+ 86: "SYS_ESTRPIPE",
+ 87: "SYS_EUSERS",
+ 88: "SYS_ENOTSOCK",
+ 89: "SYS_EDESTADDRREQ",
+ 90: "SYS_EMSGSIZE",
+ 91: "SYS_EPROTOTYPE",
+ 92: "SYS_ENOPROTOOPT",
+ 93: "SYS_EPROTONOSUPPORT",
+ 94: "SYS_ESOCKTNOSUPPORT",
+ 95: "SYS_EOPNOTSUPP",
+ // Duplicate value: 95: "SYS_ENOTSUP",
+ 96: "SYS_EPFNOSUPPORT",
+ 97: "SYS_EAFNOSUPPORT",
+ 98: "SYS_EADDRINUSE",
+ 99: "SYS_EADDRNOTAVAIL",
+ 100: "SYS_ENETDOWN",
+ 101: "SYS_ENETUNREACH",
+ 102: "SYS_ENETRESET",
+ 103: "SYS_ECONNABORTED",
+ 104: "SYS_ECONNRESET",
+ 105: "SYS_ENOBUFS",
+ 106: "SYS_EISCONN",
+ 107: "SYS_ENOTCONN",
+ 108: "SYS_ESHUTDOWN",
+ 109: "SYS_ETOOMANYREFS",
+ 110: "SYS_ETIMEDOUT",
+ 111: "SYS_ECONNREFUSED",
+ 112: "SYS_EHOSTDOWN",
+ 113: "SYS_EHOSTUNREACH",
+ 114: "SYS_EALREADY",
+ 115: "SYS_EINPROGRESS",
+ 116: "SYS_ESTALE",
+ 117: "SYS_EUCLEAN",
+ 118: "SYS_ENOTNAM",
+ 119: "SYS_ENAVAIL",
+ 120: "SYS_EISNAM",
+ 121: "SYS_EREMOTEIO",
+ 122: "SYS_EDQUOT",
+ 123: "SYS_ENOMEDIUM",
+ 124: "SYS_EMEDIUMTYPE",
+ 125: "SYS_ECANCELED",
+ 126: "SYS_ENOKEY",
+ 127: "SYS_EKEYEXPIRED",
+ 128: "SYS_EKEYREVOKED",
+ 129: "SYS_EKEYREJECTED",
+ 130: "SYS_EOWNERDEAD",
+ 131: "SYS_ENOTRECOVERABLE",
+ 132: "SYS_ERFKILL",
+}
+var RemoteSocketServiceError_SystemError_value = map[string]int32{
+ "SYS_SUCCESS": 0,
+ "SYS_EPERM": 1,
+ "SYS_ENOENT": 2,
+ "SYS_ESRCH": 3,
+ "SYS_EINTR": 4,
+ "SYS_EIO": 5,
+ "SYS_ENXIO": 6,
+ "SYS_E2BIG": 7,
+ "SYS_ENOEXEC": 8,
+ "SYS_EBADF": 9,
+ "SYS_ECHILD": 10,
+ "SYS_EAGAIN": 11,
+ "SYS_EWOULDBLOCK": 11,
+ "SYS_ENOMEM": 12,
+ "SYS_EACCES": 13,
+ "SYS_EFAULT": 14,
+ "SYS_ENOTBLK": 15,
+ "SYS_EBUSY": 16,
+ "SYS_EEXIST": 17,
+ "SYS_EXDEV": 18,
+ "SYS_ENODEV": 19,
+ "SYS_ENOTDIR": 20,
+ "SYS_EISDIR": 21,
+ "SYS_EINVAL": 22,
+ "SYS_ENFILE": 23,
+ "SYS_EMFILE": 24,
+ "SYS_ENOTTY": 25,
+ "SYS_ETXTBSY": 26,
+ "SYS_EFBIG": 27,
+ "SYS_ENOSPC": 28,
+ "SYS_ESPIPE": 29,
+ "SYS_EROFS": 30,
+ "SYS_EMLINK": 31,
+ "SYS_EPIPE": 32,
+ "SYS_EDOM": 33,
+ "SYS_ERANGE": 34,
+ "SYS_EDEADLK": 35,
+ "SYS_EDEADLOCK": 35,
+ "SYS_ENAMETOOLONG": 36,
+ "SYS_ENOLCK": 37,
+ "SYS_ENOSYS": 38,
+ "SYS_ENOTEMPTY": 39,
+ "SYS_ELOOP": 40,
+ "SYS_ENOMSG": 42,
+ "SYS_EIDRM": 43,
+ "SYS_ECHRNG": 44,
+ "SYS_EL2NSYNC": 45,
+ "SYS_EL3HLT": 46,
+ "SYS_EL3RST": 47,
+ "SYS_ELNRNG": 48,
+ "SYS_EUNATCH": 49,
+ "SYS_ENOCSI": 50,
+ "SYS_EL2HLT": 51,
+ "SYS_EBADE": 52,
+ "SYS_EBADR": 53,
+ "SYS_EXFULL": 54,
+ "SYS_ENOANO": 55,
+ "SYS_EBADRQC": 56,
+ "SYS_EBADSLT": 57,
+ "SYS_EBFONT": 59,
+ "SYS_ENOSTR": 60,
+ "SYS_ENODATA": 61,
+ "SYS_ETIME": 62,
+ "SYS_ENOSR": 63,
+ "SYS_ENONET": 64,
+ "SYS_ENOPKG": 65,
+ "SYS_EREMOTE": 66,
+ "SYS_ENOLINK": 67,
+ "SYS_EADV": 68,
+ "SYS_ESRMNT": 69,
+ "SYS_ECOMM": 70,
+ "SYS_EPROTO": 71,
+ "SYS_EMULTIHOP": 72,
+ "SYS_EDOTDOT": 73,
+ "SYS_EBADMSG": 74,
+ "SYS_EOVERFLOW": 75,
+ "SYS_ENOTUNIQ": 76,
+ "SYS_EBADFD": 77,
+ "SYS_EREMCHG": 78,
+ "SYS_ELIBACC": 79,
+ "SYS_ELIBBAD": 80,
+ "SYS_ELIBSCN": 81,
+ "SYS_ELIBMAX": 82,
+ "SYS_ELIBEXEC": 83,
+ "SYS_EILSEQ": 84,
+ "SYS_ERESTART": 85,
+ "SYS_ESTRPIPE": 86,
+ "SYS_EUSERS": 87,
+ "SYS_ENOTSOCK": 88,
+ "SYS_EDESTADDRREQ": 89,
+ "SYS_EMSGSIZE": 90,
+ "SYS_EPROTOTYPE": 91,
+ "SYS_ENOPROTOOPT": 92,
+ "SYS_EPROTONOSUPPORT": 93,
+ "SYS_ESOCKTNOSUPPORT": 94,
+ "SYS_EOPNOTSUPP": 95,
+ "SYS_ENOTSUP": 95,
+ "SYS_EPFNOSUPPORT": 96,
+ "SYS_EAFNOSUPPORT": 97,
+ "SYS_EADDRINUSE": 98,
+ "SYS_EADDRNOTAVAIL": 99,
+ "SYS_ENETDOWN": 100,
+ "SYS_ENETUNREACH": 101,
+ "SYS_ENETRESET": 102,
+ "SYS_ECONNABORTED": 103,
+ "SYS_ECONNRESET": 104,
+ "SYS_ENOBUFS": 105,
+ "SYS_EISCONN": 106,
+ "SYS_ENOTCONN": 107,
+ "SYS_ESHUTDOWN": 108,
+ "SYS_ETOOMANYREFS": 109,
+ "SYS_ETIMEDOUT": 110,
+ "SYS_ECONNREFUSED": 111,
+ "SYS_EHOSTDOWN": 112,
+ "SYS_EHOSTUNREACH": 113,
+ "SYS_EALREADY": 114,
+ "SYS_EINPROGRESS": 115,
+ "SYS_ESTALE": 116,
+ "SYS_EUCLEAN": 117,
+ "SYS_ENOTNAM": 118,
+ "SYS_ENAVAIL": 119,
+ "SYS_EISNAM": 120,
+ "SYS_EREMOTEIO": 121,
+ "SYS_EDQUOT": 122,
+ "SYS_ENOMEDIUM": 123,
+ "SYS_EMEDIUMTYPE": 124,
+ "SYS_ECANCELED": 125,
+ "SYS_ENOKEY": 126,
+ "SYS_EKEYEXPIRED": 127,
+ "SYS_EKEYREVOKED": 128,
+ "SYS_EKEYREJECTED": 129,
+ "SYS_EOWNERDEAD": 130,
+ "SYS_ENOTRECOVERABLE": 131,
+ "SYS_ERFKILL": 132,
+}
+
+func (x RemoteSocketServiceError_SystemError) Enum() *RemoteSocketServiceError_SystemError {
+ p := new(RemoteSocketServiceError_SystemError)
+ *p = x
+ return p
+}
+func (x RemoteSocketServiceError_SystemError) String() string {
+ return proto.EnumName(RemoteSocketServiceError_SystemError_name, int32(x))
+}
+func (x *RemoteSocketServiceError_SystemError) UnmarshalJSON(data []byte) error {
+ value, err := proto.UnmarshalJSONEnum(RemoteSocketServiceError_SystemError_value, data, "RemoteSocketServiceError_SystemError")
+ if err != nil {
+ return err
+ }
+ *x = RemoteSocketServiceError_SystemError(value)
+ return nil
+}
+
+type CreateSocketRequest_SocketFamily int32
+
+const (
+ CreateSocketRequest_IPv4 CreateSocketRequest_SocketFamily = 1
+ CreateSocketRequest_IPv6 CreateSocketRequest_SocketFamily = 2
+)
+
+var CreateSocketRequest_SocketFamily_name = map[int32]string{
+ 1: "IPv4",
+ 2: "IPv6",
+}
+var CreateSocketRequest_SocketFamily_value = map[string]int32{
+ "IPv4": 1,
+ "IPv6": 2,
+}
+
+func (x CreateSocketRequest_SocketFamily) Enum() *CreateSocketRequest_SocketFamily {
+ p := new(CreateSocketRequest_SocketFamily)
+ *p = x
+ return p
+}
+func (x CreateSocketRequest_SocketFamily) String() string {
+ return proto.EnumName(CreateSocketRequest_SocketFamily_name, int32(x))
+}
+func (x *CreateSocketRequest_SocketFamily) UnmarshalJSON(data []byte) error {
+ value, err := proto.UnmarshalJSONEnum(CreateSocketRequest_SocketFamily_value, data, "CreateSocketRequest_SocketFamily")
+ if err != nil {
+ return err
+ }
+ *x = CreateSocketRequest_SocketFamily(value)
+ return nil
+}
+
+type CreateSocketRequest_SocketProtocol int32
+
+const (
+ CreateSocketRequest_TCP CreateSocketRequest_SocketProtocol = 1
+ CreateSocketRequest_UDP CreateSocketRequest_SocketProtocol = 2
+)
+
+var CreateSocketRequest_SocketProtocol_name = map[int32]string{
+ 1: "TCP",
+ 2: "UDP",
+}
+var CreateSocketRequest_SocketProtocol_value = map[string]int32{
+ "TCP": 1,
+ "UDP": 2,
+}
+
+func (x CreateSocketRequest_SocketProtocol) Enum() *CreateSocketRequest_SocketProtocol {
+ p := new(CreateSocketRequest_SocketProtocol)
+ *p = x
+ return p
+}
+func (x CreateSocketRequest_SocketProtocol) String() string {
+ return proto.EnumName(CreateSocketRequest_SocketProtocol_name, int32(x))
+}
+func (x *CreateSocketRequest_SocketProtocol) UnmarshalJSON(data []byte) error {
+ value, err := proto.UnmarshalJSONEnum(CreateSocketRequest_SocketProtocol_value, data, "CreateSocketRequest_SocketProtocol")
+ if err != nil {
+ return err
+ }
+ *x = CreateSocketRequest_SocketProtocol(value)
+ return nil
+}
+
+type SocketOption_SocketOptionLevel int32
+
+const (
+ SocketOption_SOCKET_SOL_IP SocketOption_SocketOptionLevel = 0
+ SocketOption_SOCKET_SOL_SOCKET SocketOption_SocketOptionLevel = 1
+ SocketOption_SOCKET_SOL_TCP SocketOption_SocketOptionLevel = 6
+ SocketOption_SOCKET_SOL_UDP SocketOption_SocketOptionLevel = 17
+)
+
+var SocketOption_SocketOptionLevel_name = map[int32]string{
+ 0: "SOCKET_SOL_IP",
+ 1: "SOCKET_SOL_SOCKET",
+ 6: "SOCKET_SOL_TCP",
+ 17: "SOCKET_SOL_UDP",
+}
+var SocketOption_SocketOptionLevel_value = map[string]int32{
+ "SOCKET_SOL_IP": 0,
+ "SOCKET_SOL_SOCKET": 1,
+ "SOCKET_SOL_TCP": 6,
+ "SOCKET_SOL_UDP": 17,
+}
+
+func (x SocketOption_SocketOptionLevel) Enum() *SocketOption_SocketOptionLevel {
+ p := new(SocketOption_SocketOptionLevel)
+ *p = x
+ return p
+}
+func (x SocketOption_SocketOptionLevel) String() string {
+ return proto.EnumName(SocketOption_SocketOptionLevel_name, int32(x))
+}
+func (x *SocketOption_SocketOptionLevel) UnmarshalJSON(data []byte) error {
+ value, err := proto.UnmarshalJSONEnum(SocketOption_SocketOptionLevel_value, data, "SocketOption_SocketOptionLevel")
+ if err != nil {
+ return err
+ }
+ *x = SocketOption_SocketOptionLevel(value)
+ return nil
+}
+
+type SocketOption_SocketOptionName int32
+
+const (
+ SocketOption_SOCKET_SO_DEBUG SocketOption_SocketOptionName = 1
+ SocketOption_SOCKET_SO_REUSEADDR SocketOption_SocketOptionName = 2
+ SocketOption_SOCKET_SO_TYPE SocketOption_SocketOptionName = 3
+ SocketOption_SOCKET_SO_ERROR SocketOption_SocketOptionName = 4
+ SocketOption_SOCKET_SO_DONTROUTE SocketOption_SocketOptionName = 5
+ SocketOption_SOCKET_SO_BROADCAST SocketOption_SocketOptionName = 6
+ SocketOption_SOCKET_SO_SNDBUF SocketOption_SocketOptionName = 7
+ SocketOption_SOCKET_SO_RCVBUF SocketOption_SocketOptionName = 8
+ SocketOption_SOCKET_SO_KEEPALIVE SocketOption_SocketOptionName = 9
+ SocketOption_SOCKET_SO_OOBINLINE SocketOption_SocketOptionName = 10
+ SocketOption_SOCKET_SO_LINGER SocketOption_SocketOptionName = 13
+ SocketOption_SOCKET_SO_RCVTIMEO SocketOption_SocketOptionName = 20
+ SocketOption_SOCKET_SO_SNDTIMEO SocketOption_SocketOptionName = 21
+ SocketOption_SOCKET_IP_TOS SocketOption_SocketOptionName = 1
+ SocketOption_SOCKET_IP_TTL SocketOption_SocketOptionName = 2
+ SocketOption_SOCKET_IP_HDRINCL SocketOption_SocketOptionName = 3
+ SocketOption_SOCKET_IP_OPTIONS SocketOption_SocketOptionName = 4
+ SocketOption_SOCKET_TCP_NODELAY SocketOption_SocketOptionName = 1
+ SocketOption_SOCKET_TCP_MAXSEG SocketOption_SocketOptionName = 2
+ SocketOption_SOCKET_TCP_CORK SocketOption_SocketOptionName = 3
+ SocketOption_SOCKET_TCP_KEEPIDLE SocketOption_SocketOptionName = 4
+ SocketOption_SOCKET_TCP_KEEPINTVL SocketOption_SocketOptionName = 5
+ SocketOption_SOCKET_TCP_KEEPCNT SocketOption_SocketOptionName = 6
+ SocketOption_SOCKET_TCP_SYNCNT SocketOption_SocketOptionName = 7
+ SocketOption_SOCKET_TCP_LINGER2 SocketOption_SocketOptionName = 8
+ SocketOption_SOCKET_TCP_DEFER_ACCEPT SocketOption_SocketOptionName = 9
+ SocketOption_SOCKET_TCP_WINDOW_CLAMP SocketOption_SocketOptionName = 10
+ SocketOption_SOCKET_TCP_INFO SocketOption_SocketOptionName = 11
+ SocketOption_SOCKET_TCP_QUICKACK SocketOption_SocketOptionName = 12
+)
+
+var SocketOption_SocketOptionName_name = map[int32]string{
+ 1: "SOCKET_SO_DEBUG",
+ 2: "SOCKET_SO_REUSEADDR",
+ 3: "SOCKET_SO_TYPE",
+ 4: "SOCKET_SO_ERROR",
+ 5: "SOCKET_SO_DONTROUTE",
+ 6: "SOCKET_SO_BROADCAST",
+ 7: "SOCKET_SO_SNDBUF",
+ 8: "SOCKET_SO_RCVBUF",
+ 9: "SOCKET_SO_KEEPALIVE",
+ 10: "SOCKET_SO_OOBINLINE",
+ 13: "SOCKET_SO_LINGER",
+ 20: "SOCKET_SO_RCVTIMEO",
+ 21: "SOCKET_SO_SNDTIMEO",
+ // Duplicate value: 1: "SOCKET_IP_TOS",
+ // Duplicate value: 2: "SOCKET_IP_TTL",
+ // Duplicate value: 3: "SOCKET_IP_HDRINCL",
+ // Duplicate value: 4: "SOCKET_IP_OPTIONS",
+ // Duplicate value: 1: "SOCKET_TCP_NODELAY",
+ // Duplicate value: 2: "SOCKET_TCP_MAXSEG",
+ // Duplicate value: 3: "SOCKET_TCP_CORK",
+ // Duplicate value: 4: "SOCKET_TCP_KEEPIDLE",
+ // Duplicate value: 5: "SOCKET_TCP_KEEPINTVL",
+ // Duplicate value: 6: "SOCKET_TCP_KEEPCNT",
+ // Duplicate value: 7: "SOCKET_TCP_SYNCNT",
+ // Duplicate value: 8: "SOCKET_TCP_LINGER2",
+ // Duplicate value: 9: "SOCKET_TCP_DEFER_ACCEPT",
+ // Duplicate value: 10: "SOCKET_TCP_WINDOW_CLAMP",
+ 11: "SOCKET_TCP_INFO",
+ 12: "SOCKET_TCP_QUICKACK",
+}
+var SocketOption_SocketOptionName_value = map[string]int32{
+ "SOCKET_SO_DEBUG": 1,
+ "SOCKET_SO_REUSEADDR": 2,
+ "SOCKET_SO_TYPE": 3,
+ "SOCKET_SO_ERROR": 4,
+ "SOCKET_SO_DONTROUTE": 5,
+ "SOCKET_SO_BROADCAST": 6,
+ "SOCKET_SO_SNDBUF": 7,
+ "SOCKET_SO_RCVBUF": 8,
+ "SOCKET_SO_KEEPALIVE": 9,
+ "SOCKET_SO_OOBINLINE": 10,
+ "SOCKET_SO_LINGER": 13,
+ "SOCKET_SO_RCVTIMEO": 20,
+ "SOCKET_SO_SNDTIMEO": 21,
+ "SOCKET_IP_TOS": 1,
+ "SOCKET_IP_TTL": 2,
+ "SOCKET_IP_HDRINCL": 3,
+ "SOCKET_IP_OPTIONS": 4,
+ "SOCKET_TCP_NODELAY": 1,
+ "SOCKET_TCP_MAXSEG": 2,
+ "SOCKET_TCP_CORK": 3,
+ "SOCKET_TCP_KEEPIDLE": 4,
+ "SOCKET_TCP_KEEPINTVL": 5,
+ "SOCKET_TCP_KEEPCNT": 6,
+ "SOCKET_TCP_SYNCNT": 7,
+ "SOCKET_TCP_LINGER2": 8,
+ "SOCKET_TCP_DEFER_ACCEPT": 9,
+ "SOCKET_TCP_WINDOW_CLAMP": 10,
+ "SOCKET_TCP_INFO": 11,
+ "SOCKET_TCP_QUICKACK": 12,
+}
+
+func (x SocketOption_SocketOptionName) Enum() *SocketOption_SocketOptionName {
+ p := new(SocketOption_SocketOptionName)
+ *p = x
+ return p
+}
+func (x SocketOption_SocketOptionName) String() string {
+ return proto.EnumName(SocketOption_SocketOptionName_name, int32(x))
+}
+func (x *SocketOption_SocketOptionName) UnmarshalJSON(data []byte) error {
+ value, err := proto.UnmarshalJSONEnum(SocketOption_SocketOptionName_value, data, "SocketOption_SocketOptionName")
+ if err != nil {
+ return err
+ }
+ *x = SocketOption_SocketOptionName(value)
+ return nil
+}
+
+type ShutDownRequest_How int32
+
+const (
+ ShutDownRequest_SOCKET_SHUT_RD ShutDownRequest_How = 1
+ ShutDownRequest_SOCKET_SHUT_WR ShutDownRequest_How = 2
+ ShutDownRequest_SOCKET_SHUT_RDWR ShutDownRequest_How = 3
+)
+
+var ShutDownRequest_How_name = map[int32]string{
+ 1: "SOCKET_SHUT_RD",
+ 2: "SOCKET_SHUT_WR",
+ 3: "SOCKET_SHUT_RDWR",
+}
+var ShutDownRequest_How_value = map[string]int32{
+ "SOCKET_SHUT_RD": 1,
+ "SOCKET_SHUT_WR": 2,
+ "SOCKET_SHUT_RDWR": 3,
+}
+
+func (x ShutDownRequest_How) Enum() *ShutDownRequest_How {
+ p := new(ShutDownRequest_How)
+ *p = x
+ return p
+}
+func (x ShutDownRequest_How) String() string {
+ return proto.EnumName(ShutDownRequest_How_name, int32(x))
+}
+func (x *ShutDownRequest_How) UnmarshalJSON(data []byte) error {
+ value, err := proto.UnmarshalJSONEnum(ShutDownRequest_How_value, data, "ShutDownRequest_How")
+ if err != nil {
+ return err
+ }
+ *x = ShutDownRequest_How(value)
+ return nil
+}
+
+type ReceiveRequest_Flags int32
+
+const (
+ ReceiveRequest_MSG_OOB ReceiveRequest_Flags = 1
+ ReceiveRequest_MSG_PEEK ReceiveRequest_Flags = 2
+)
+
+var ReceiveRequest_Flags_name = map[int32]string{
+ 1: "MSG_OOB",
+ 2: "MSG_PEEK",
+}
+var ReceiveRequest_Flags_value = map[string]int32{
+ "MSG_OOB": 1,
+ "MSG_PEEK": 2,
+}
+
+func (x ReceiveRequest_Flags) Enum() *ReceiveRequest_Flags {
+ p := new(ReceiveRequest_Flags)
+ *p = x
+ return p
+}
+func (x ReceiveRequest_Flags) String() string {
+ return proto.EnumName(ReceiveRequest_Flags_name, int32(x))
+}
+func (x *ReceiveRequest_Flags) UnmarshalJSON(data []byte) error {
+ value, err := proto.UnmarshalJSONEnum(ReceiveRequest_Flags_value, data, "ReceiveRequest_Flags")
+ if err != nil {
+ return err
+ }
+ *x = ReceiveRequest_Flags(value)
+ return nil
+}
+
+type PollEvent_PollEventFlag int32
+
+const (
+ PollEvent_SOCKET_POLLNONE PollEvent_PollEventFlag = 0
+ PollEvent_SOCKET_POLLIN PollEvent_PollEventFlag = 1
+ PollEvent_SOCKET_POLLPRI PollEvent_PollEventFlag = 2
+ PollEvent_SOCKET_POLLOUT PollEvent_PollEventFlag = 4
+ PollEvent_SOCKET_POLLERR PollEvent_PollEventFlag = 8
+ PollEvent_SOCKET_POLLHUP PollEvent_PollEventFlag = 16
+ PollEvent_SOCKET_POLLNVAL PollEvent_PollEventFlag = 32
+ PollEvent_SOCKET_POLLRDNORM PollEvent_PollEventFlag = 64
+ PollEvent_SOCKET_POLLRDBAND PollEvent_PollEventFlag = 128
+ PollEvent_SOCKET_POLLWRNORM PollEvent_PollEventFlag = 256
+ PollEvent_SOCKET_POLLWRBAND PollEvent_PollEventFlag = 512
+ PollEvent_SOCKET_POLLMSG PollEvent_PollEventFlag = 1024
+ PollEvent_SOCKET_POLLREMOVE PollEvent_PollEventFlag = 4096
+ PollEvent_SOCKET_POLLRDHUP PollEvent_PollEventFlag = 8192
+)
+
+var PollEvent_PollEventFlag_name = map[int32]string{
+ 0: "SOCKET_POLLNONE",
+ 1: "SOCKET_POLLIN",
+ 2: "SOCKET_POLLPRI",
+ 4: "SOCKET_POLLOUT",
+ 8: "SOCKET_POLLERR",
+ 16: "SOCKET_POLLHUP",
+ 32: "SOCKET_POLLNVAL",
+ 64: "SOCKET_POLLRDNORM",
+ 128: "SOCKET_POLLRDBAND",
+ 256: "SOCKET_POLLWRNORM",
+ 512: "SOCKET_POLLWRBAND",
+ 1024: "SOCKET_POLLMSG",
+ 4096: "SOCKET_POLLREMOVE",
+ 8192: "SOCKET_POLLRDHUP",
+}
+var PollEvent_PollEventFlag_value = map[string]int32{
+ "SOCKET_POLLNONE": 0,
+ "SOCKET_POLLIN": 1,
+ "SOCKET_POLLPRI": 2,
+ "SOCKET_POLLOUT": 4,
+ "SOCKET_POLLERR": 8,
+ "SOCKET_POLLHUP": 16,
+ "SOCKET_POLLNVAL": 32,
+ "SOCKET_POLLRDNORM": 64,
+ "SOCKET_POLLRDBAND": 128,
+ "SOCKET_POLLWRNORM": 256,
+ "SOCKET_POLLWRBAND": 512,
+ "SOCKET_POLLMSG": 1024,
+ "SOCKET_POLLREMOVE": 4096,
+ "SOCKET_POLLRDHUP": 8192,
+}
+
+func (x PollEvent_PollEventFlag) Enum() *PollEvent_PollEventFlag {
+ p := new(PollEvent_PollEventFlag)
+ *p = x
+ return p
+}
+func (x PollEvent_PollEventFlag) String() string {
+ return proto.EnumName(PollEvent_PollEventFlag_name, int32(x))
+}
+func (x *PollEvent_PollEventFlag) UnmarshalJSON(data []byte) error {
+ value, err := proto.UnmarshalJSONEnum(PollEvent_PollEventFlag_value, data, "PollEvent_PollEventFlag")
+ if err != nil {
+ return err
+ }
+ *x = PollEvent_PollEventFlag(value)
+ return nil
+}
+
+type ResolveReply_ErrorCode int32
+
+const (
+ ResolveReply_SOCKET_EAI_ADDRFAMILY ResolveReply_ErrorCode = 1
+ ResolveReply_SOCKET_EAI_AGAIN ResolveReply_ErrorCode = 2
+ ResolveReply_SOCKET_EAI_BADFLAGS ResolveReply_ErrorCode = 3
+ ResolveReply_SOCKET_EAI_FAIL ResolveReply_ErrorCode = 4
+ ResolveReply_SOCKET_EAI_FAMILY ResolveReply_ErrorCode = 5
+ ResolveReply_SOCKET_EAI_MEMORY ResolveReply_ErrorCode = 6
+ ResolveReply_SOCKET_EAI_NODATA ResolveReply_ErrorCode = 7
+ ResolveReply_SOCKET_EAI_NONAME ResolveReply_ErrorCode = 8
+ ResolveReply_SOCKET_EAI_SERVICE ResolveReply_ErrorCode = 9
+ ResolveReply_SOCKET_EAI_SOCKTYPE ResolveReply_ErrorCode = 10
+ ResolveReply_SOCKET_EAI_SYSTEM ResolveReply_ErrorCode = 11
+ ResolveReply_SOCKET_EAI_BADHINTS ResolveReply_ErrorCode = 12
+ ResolveReply_SOCKET_EAI_PROTOCOL ResolveReply_ErrorCode = 13
+ ResolveReply_SOCKET_EAI_OVERFLOW ResolveReply_ErrorCode = 14
+ ResolveReply_SOCKET_EAI_MAX ResolveReply_ErrorCode = 15
+)
+
+var ResolveReply_ErrorCode_name = map[int32]string{
+ 1: "SOCKET_EAI_ADDRFAMILY",
+ 2: "SOCKET_EAI_AGAIN",
+ 3: "SOCKET_EAI_BADFLAGS",
+ 4: "SOCKET_EAI_FAIL",
+ 5: "SOCKET_EAI_FAMILY",
+ 6: "SOCKET_EAI_MEMORY",
+ 7: "SOCKET_EAI_NODATA",
+ 8: "SOCKET_EAI_NONAME",
+ 9: "SOCKET_EAI_SERVICE",
+ 10: "SOCKET_EAI_SOCKTYPE",
+ 11: "SOCKET_EAI_SYSTEM",
+ 12: "SOCKET_EAI_BADHINTS",
+ 13: "SOCKET_EAI_PROTOCOL",
+ 14: "SOCKET_EAI_OVERFLOW",
+ 15: "SOCKET_EAI_MAX",
+}
+var ResolveReply_ErrorCode_value = map[string]int32{
+ "SOCKET_EAI_ADDRFAMILY": 1,
+ "SOCKET_EAI_AGAIN": 2,
+ "SOCKET_EAI_BADFLAGS": 3,
+ "SOCKET_EAI_FAIL": 4,
+ "SOCKET_EAI_FAMILY": 5,
+ "SOCKET_EAI_MEMORY": 6,
+ "SOCKET_EAI_NODATA": 7,
+ "SOCKET_EAI_NONAME": 8,
+ "SOCKET_EAI_SERVICE": 9,
+ "SOCKET_EAI_SOCKTYPE": 10,
+ "SOCKET_EAI_SYSTEM": 11,
+ "SOCKET_EAI_BADHINTS": 12,
+ "SOCKET_EAI_PROTOCOL": 13,
+ "SOCKET_EAI_OVERFLOW": 14,
+ "SOCKET_EAI_MAX": 15,
+}
+
+func (x ResolveReply_ErrorCode) Enum() *ResolveReply_ErrorCode {
+ p := new(ResolveReply_ErrorCode)
+ *p = x
+ return p
+}
+func (x ResolveReply_ErrorCode) String() string {
+ return proto.EnumName(ResolveReply_ErrorCode_name, int32(x))
+}
+func (x *ResolveReply_ErrorCode) UnmarshalJSON(data []byte) error {
+ value, err := proto.UnmarshalJSONEnum(ResolveReply_ErrorCode_value, data, "ResolveReply_ErrorCode")
+ if err != nil {
+ return err
+ }
+ *x = ResolveReply_ErrorCode(value)
+ return nil
+}
+
+type RemoteSocketServiceError struct {
+ SystemError *int32 `protobuf:"varint,1,opt,name=system_error,def=0" json:"system_error,omitempty"`
+ ErrorDetail *string `protobuf:"bytes,2,opt,name=error_detail" json:"error_detail,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *RemoteSocketServiceError) Reset() { *m = RemoteSocketServiceError{} }
+func (m *RemoteSocketServiceError) String() string { return proto.CompactTextString(m) }
+func (*RemoteSocketServiceError) ProtoMessage() {}
+
+const Default_RemoteSocketServiceError_SystemError int32 = 0
+
+func (m *RemoteSocketServiceError) GetSystemError() int32 {
+ if m != nil && m.SystemError != nil {
+ return *m.SystemError
+ }
+ return Default_RemoteSocketServiceError_SystemError
+}
+
+func (m *RemoteSocketServiceError) GetErrorDetail() string {
+ if m != nil && m.ErrorDetail != nil {
+ return *m.ErrorDetail
+ }
+ return ""
+}
+
+type AddressPort struct {
+ Port *int32 `protobuf:"varint,1,req,name=port" json:"port,omitempty"`
+ PackedAddress []byte `protobuf:"bytes,2,opt,name=packed_address" json:"packed_address,omitempty"`
+ HostnameHint *string `protobuf:"bytes,3,opt,name=hostname_hint" json:"hostname_hint,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *AddressPort) Reset() { *m = AddressPort{} }
+func (m *AddressPort) String() string { return proto.CompactTextString(m) }
+func (*AddressPort) ProtoMessage() {}
+
+func (m *AddressPort) GetPort() int32 {
+ if m != nil && m.Port != nil {
+ return *m.Port
+ }
+ return 0
+}
+
+func (m *AddressPort) GetPackedAddress() []byte {
+ if m != nil {
+ return m.PackedAddress
+ }
+ return nil
+}
+
+func (m *AddressPort) GetHostnameHint() string {
+ if m != nil && m.HostnameHint != nil {
+ return *m.HostnameHint
+ }
+ return ""
+}
+
+type CreateSocketRequest struct {
+ Family *CreateSocketRequest_SocketFamily `protobuf:"varint,1,req,name=family,enum=appengine.CreateSocketRequest_SocketFamily" json:"family,omitempty"`
+ Protocol *CreateSocketRequest_SocketProtocol `protobuf:"varint,2,req,name=protocol,enum=appengine.CreateSocketRequest_SocketProtocol" json:"protocol,omitempty"`
+ SocketOptions []*SocketOption `protobuf:"bytes,3,rep,name=socket_options" json:"socket_options,omitempty"`
+ ProxyExternalIp *AddressPort `protobuf:"bytes,4,opt,name=proxy_external_ip" json:"proxy_external_ip,omitempty"`
+ ListenBacklog *int32 `protobuf:"varint,5,opt,name=listen_backlog,def=0" json:"listen_backlog,omitempty"`
+ RemoteIp *AddressPort `protobuf:"bytes,6,opt,name=remote_ip" json:"remote_ip,omitempty"`
+ AppId *string `protobuf:"bytes,9,opt,name=app_id" json:"app_id,omitempty"`
+ ProjectId *int64 `protobuf:"varint,10,opt,name=project_id" json:"project_id,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *CreateSocketRequest) Reset() { *m = CreateSocketRequest{} }
+func (m *CreateSocketRequest) String() string { return proto.CompactTextString(m) }
+func (*CreateSocketRequest) ProtoMessage() {}
+
+const Default_CreateSocketRequest_ListenBacklog int32 = 0
+
+func (m *CreateSocketRequest) GetFamily() CreateSocketRequest_SocketFamily {
+ if m != nil && m.Family != nil {
+ return *m.Family
+ }
+ return CreateSocketRequest_IPv4
+}
+
+func (m *CreateSocketRequest) GetProtocol() CreateSocketRequest_SocketProtocol {
+ if m != nil && m.Protocol != nil {
+ return *m.Protocol
+ }
+ return CreateSocketRequest_TCP
+}
+
+func (m *CreateSocketRequest) GetSocketOptions() []*SocketOption {
+ if m != nil {
+ return m.SocketOptions
+ }
+ return nil
+}
+
+func (m *CreateSocketRequest) GetProxyExternalIp() *AddressPort {
+ if m != nil {
+ return m.ProxyExternalIp
+ }
+ return nil
+}
+
+func (m *CreateSocketRequest) GetListenBacklog() int32 {
+ if m != nil && m.ListenBacklog != nil {
+ return *m.ListenBacklog
+ }
+ return Default_CreateSocketRequest_ListenBacklog
+}
+
+func (m *CreateSocketRequest) GetRemoteIp() *AddressPort {
+ if m != nil {
+ return m.RemoteIp
+ }
+ return nil
+}
+
+func (m *CreateSocketRequest) GetAppId() string {
+ if m != nil && m.AppId != nil {
+ return *m.AppId
+ }
+ return ""
+}
+
+func (m *CreateSocketRequest) GetProjectId() int64 {
+ if m != nil && m.ProjectId != nil {
+ return *m.ProjectId
+ }
+ return 0
+}
+
+type CreateSocketReply struct {
+ SocketDescriptor *string `protobuf:"bytes,1,opt,name=socket_descriptor" json:"socket_descriptor,omitempty"`
+ ServerAddress *AddressPort `protobuf:"bytes,3,opt,name=server_address" json:"server_address,omitempty"`
+ ProxyExternalIp *AddressPort `protobuf:"bytes,4,opt,name=proxy_external_ip" json:"proxy_external_ip,omitempty"`
+ XXX_extensions map[int32]proto.Extension `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *CreateSocketReply) Reset() { *m = CreateSocketReply{} }
+func (m *CreateSocketReply) String() string { return proto.CompactTextString(m) }
+func (*CreateSocketReply) ProtoMessage() {}
+
+var extRange_CreateSocketReply = []proto.ExtensionRange{
+ {1000, 536870911},
+}
+
+func (*CreateSocketReply) ExtensionRangeArray() []proto.ExtensionRange {
+ return extRange_CreateSocketReply
+}
+func (m *CreateSocketReply) ExtensionMap() map[int32]proto.Extension {
+ if m.XXX_extensions == nil {
+ m.XXX_extensions = make(map[int32]proto.Extension)
+ }
+ return m.XXX_extensions
+}
+
+func (m *CreateSocketReply) GetSocketDescriptor() string {
+ if m != nil && m.SocketDescriptor != nil {
+ return *m.SocketDescriptor
+ }
+ return ""
+}
+
+func (m *CreateSocketReply) GetServerAddress() *AddressPort {
+ if m != nil {
+ return m.ServerAddress
+ }
+ return nil
+}
+
+func (m *CreateSocketReply) GetProxyExternalIp() *AddressPort {
+ if m != nil {
+ return m.ProxyExternalIp
+ }
+ return nil
+}
+
+type BindRequest struct {
+ SocketDescriptor *string `protobuf:"bytes,1,req,name=socket_descriptor" json:"socket_descriptor,omitempty"`
+ ProxyExternalIp *AddressPort `protobuf:"bytes,2,req,name=proxy_external_ip" json:"proxy_external_ip,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *BindRequest) Reset() { *m = BindRequest{} }
+func (m *BindRequest) String() string { return proto.CompactTextString(m) }
+func (*BindRequest) ProtoMessage() {}
+
+func (m *BindRequest) GetSocketDescriptor() string {
+ if m != nil && m.SocketDescriptor != nil {
+ return *m.SocketDescriptor
+ }
+ return ""
+}
+
+func (m *BindRequest) GetProxyExternalIp() *AddressPort {
+ if m != nil {
+ return m.ProxyExternalIp
+ }
+ return nil
+}
+
+type BindReply struct {
+ ProxyExternalIp *AddressPort `protobuf:"bytes,1,opt,name=proxy_external_ip" json:"proxy_external_ip,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *BindReply) Reset() { *m = BindReply{} }
+func (m *BindReply) String() string { return proto.CompactTextString(m) }
+func (*BindReply) ProtoMessage() {}
+
+func (m *BindReply) GetProxyExternalIp() *AddressPort {
+ if m != nil {
+ return m.ProxyExternalIp
+ }
+ return nil
+}
+
+type GetSocketNameRequest struct {
+ SocketDescriptor *string `protobuf:"bytes,1,req,name=socket_descriptor" json:"socket_descriptor,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *GetSocketNameRequest) Reset() { *m = GetSocketNameRequest{} }
+func (m *GetSocketNameRequest) String() string { return proto.CompactTextString(m) }
+func (*GetSocketNameRequest) ProtoMessage() {}
+
+func (m *GetSocketNameRequest) GetSocketDescriptor() string {
+ if m != nil && m.SocketDescriptor != nil {
+ return *m.SocketDescriptor
+ }
+ return ""
+}
+
+type GetSocketNameReply struct {
+ ProxyExternalIp *AddressPort `protobuf:"bytes,2,opt,name=proxy_external_ip" json:"proxy_external_ip,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *GetSocketNameReply) Reset() { *m = GetSocketNameReply{} }
+func (m *GetSocketNameReply) String() string { return proto.CompactTextString(m) }
+func (*GetSocketNameReply) ProtoMessage() {}
+
+func (m *GetSocketNameReply) GetProxyExternalIp() *AddressPort {
+ if m != nil {
+ return m.ProxyExternalIp
+ }
+ return nil
+}
+
+type GetPeerNameRequest struct {
+ SocketDescriptor *string `protobuf:"bytes,1,req,name=socket_descriptor" json:"socket_descriptor,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *GetPeerNameRequest) Reset() { *m = GetPeerNameRequest{} }
+func (m *GetPeerNameRequest) String() string { return proto.CompactTextString(m) }
+func (*GetPeerNameRequest) ProtoMessage() {}
+
+func (m *GetPeerNameRequest) GetSocketDescriptor() string {
+ if m != nil && m.SocketDescriptor != nil {
+ return *m.SocketDescriptor
+ }
+ return ""
+}
+
+type GetPeerNameReply struct {
+ PeerIp *AddressPort `protobuf:"bytes,2,opt,name=peer_ip" json:"peer_ip,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *GetPeerNameReply) Reset() { *m = GetPeerNameReply{} }
+func (m *GetPeerNameReply) String() string { return proto.CompactTextString(m) }
+func (*GetPeerNameReply) ProtoMessage() {}
+
+func (m *GetPeerNameReply) GetPeerIp() *AddressPort {
+ if m != nil {
+ return m.PeerIp
+ }
+ return nil
+}
+
+type SocketOption struct {
+ Level *SocketOption_SocketOptionLevel `protobuf:"varint,1,req,name=level,enum=appengine.SocketOption_SocketOptionLevel" json:"level,omitempty"`
+ Option *SocketOption_SocketOptionName `protobuf:"varint,2,req,name=option,enum=appengine.SocketOption_SocketOptionName" json:"option,omitempty"`
+ Value []byte `protobuf:"bytes,3,req,name=value" json:"value,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *SocketOption) Reset() { *m = SocketOption{} }
+func (m *SocketOption) String() string { return proto.CompactTextString(m) }
+func (*SocketOption) ProtoMessage() {}
+
+func (m *SocketOption) GetLevel() SocketOption_SocketOptionLevel {
+ if m != nil && m.Level != nil {
+ return *m.Level
+ }
+ return SocketOption_SOCKET_SOL_IP
+}
+
+func (m *SocketOption) GetOption() SocketOption_SocketOptionName {
+ if m != nil && m.Option != nil {
+ return *m.Option
+ }
+ return SocketOption_SOCKET_SO_DEBUG
+}
+
+func (m *SocketOption) GetValue() []byte {
+ if m != nil {
+ return m.Value
+ }
+ return nil
+}
+
+type SetSocketOptionsRequest struct {
+ SocketDescriptor *string `protobuf:"bytes,1,req,name=socket_descriptor" json:"socket_descriptor,omitempty"`
+ Options []*SocketOption `protobuf:"bytes,2,rep,name=options" json:"options,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *SetSocketOptionsRequest) Reset() { *m = SetSocketOptionsRequest{} }
+func (m *SetSocketOptionsRequest) String() string { return proto.CompactTextString(m) }
+func (*SetSocketOptionsRequest) ProtoMessage() {}
+
+func (m *SetSocketOptionsRequest) GetSocketDescriptor() string {
+ if m != nil && m.SocketDescriptor != nil {
+ return *m.SocketDescriptor
+ }
+ return ""
+}
+
+func (m *SetSocketOptionsRequest) GetOptions() []*SocketOption {
+ if m != nil {
+ return m.Options
+ }
+ return nil
+}
+
+type SetSocketOptionsReply struct {
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *SetSocketOptionsReply) Reset() { *m = SetSocketOptionsReply{} }
+func (m *SetSocketOptionsReply) String() string { return proto.CompactTextString(m) }
+func (*SetSocketOptionsReply) ProtoMessage() {}
+
+type GetSocketOptionsRequest struct {
+ SocketDescriptor *string `protobuf:"bytes,1,req,name=socket_descriptor" json:"socket_descriptor,omitempty"`
+ Options []*SocketOption `protobuf:"bytes,2,rep,name=options" json:"options,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *GetSocketOptionsRequest) Reset() { *m = GetSocketOptionsRequest{} }
+func (m *GetSocketOptionsRequest) String() string { return proto.CompactTextString(m) }
+func (*GetSocketOptionsRequest) ProtoMessage() {}
+
+func (m *GetSocketOptionsRequest) GetSocketDescriptor() string {
+ if m != nil && m.SocketDescriptor != nil {
+ return *m.SocketDescriptor
+ }
+ return ""
+}
+
+func (m *GetSocketOptionsRequest) GetOptions() []*SocketOption {
+ if m != nil {
+ return m.Options
+ }
+ return nil
+}
+
+type GetSocketOptionsReply struct {
+ Options []*SocketOption `protobuf:"bytes,2,rep,name=options" json:"options,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *GetSocketOptionsReply) Reset() { *m = GetSocketOptionsReply{} }
+func (m *GetSocketOptionsReply) String() string { return proto.CompactTextString(m) }
+func (*GetSocketOptionsReply) ProtoMessage() {}
+
+func (m *GetSocketOptionsReply) GetOptions() []*SocketOption {
+ if m != nil {
+ return m.Options
+ }
+ return nil
+}
+
+type ConnectRequest struct {
+ SocketDescriptor *string `protobuf:"bytes,1,req,name=socket_descriptor" json:"socket_descriptor,omitempty"`
+ RemoteIp *AddressPort `protobuf:"bytes,2,req,name=remote_ip" json:"remote_ip,omitempty"`
+ TimeoutSeconds *float64 `protobuf:"fixed64,3,opt,name=timeout_seconds,def=-1" json:"timeout_seconds,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *ConnectRequest) Reset() { *m = ConnectRequest{} }
+func (m *ConnectRequest) String() string { return proto.CompactTextString(m) }
+func (*ConnectRequest) ProtoMessage() {}
+
+const Default_ConnectRequest_TimeoutSeconds float64 = -1
+
+func (m *ConnectRequest) GetSocketDescriptor() string {
+ if m != nil && m.SocketDescriptor != nil {
+ return *m.SocketDescriptor
+ }
+ return ""
+}
+
+func (m *ConnectRequest) GetRemoteIp() *AddressPort {
+ if m != nil {
+ return m.RemoteIp
+ }
+ return nil
+}
+
+func (m *ConnectRequest) GetTimeoutSeconds() float64 {
+ if m != nil && m.TimeoutSeconds != nil {
+ return *m.TimeoutSeconds
+ }
+ return Default_ConnectRequest_TimeoutSeconds
+}
+
+type ConnectReply struct {
+ ProxyExternalIp *AddressPort `protobuf:"bytes,1,opt,name=proxy_external_ip" json:"proxy_external_ip,omitempty"`
+ XXX_extensions map[int32]proto.Extension `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *ConnectReply) Reset() { *m = ConnectReply{} }
+func (m *ConnectReply) String() string { return proto.CompactTextString(m) }
+func (*ConnectReply) ProtoMessage() {}
+
+var extRange_ConnectReply = []proto.ExtensionRange{
+ {1000, 536870911},
+}
+
+func (*ConnectReply) ExtensionRangeArray() []proto.ExtensionRange {
+ return extRange_ConnectReply
+}
+func (m *ConnectReply) ExtensionMap() map[int32]proto.Extension {
+ if m.XXX_extensions == nil {
+ m.XXX_extensions = make(map[int32]proto.Extension)
+ }
+ return m.XXX_extensions
+}
+
+func (m *ConnectReply) GetProxyExternalIp() *AddressPort {
+ if m != nil {
+ return m.ProxyExternalIp
+ }
+ return nil
+}
+
+type ListenRequest struct {
+ SocketDescriptor *string `protobuf:"bytes,1,req,name=socket_descriptor" json:"socket_descriptor,omitempty"`
+ Backlog *int32 `protobuf:"varint,2,req,name=backlog" json:"backlog,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *ListenRequest) Reset() { *m = ListenRequest{} }
+func (m *ListenRequest) String() string { return proto.CompactTextString(m) }
+func (*ListenRequest) ProtoMessage() {}
+
+func (m *ListenRequest) GetSocketDescriptor() string {
+ if m != nil && m.SocketDescriptor != nil {
+ return *m.SocketDescriptor
+ }
+ return ""
+}
+
+func (m *ListenRequest) GetBacklog() int32 {
+ if m != nil && m.Backlog != nil {
+ return *m.Backlog
+ }
+ return 0
+}
+
+type ListenReply struct {
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *ListenReply) Reset() { *m = ListenReply{} }
+func (m *ListenReply) String() string { return proto.CompactTextString(m) }
+func (*ListenReply) ProtoMessage() {}
+
+type AcceptRequest struct {
+ SocketDescriptor *string `protobuf:"bytes,1,req,name=socket_descriptor" json:"socket_descriptor,omitempty"`
+ TimeoutSeconds *float64 `protobuf:"fixed64,2,opt,name=timeout_seconds,def=-1" json:"timeout_seconds,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *AcceptRequest) Reset() { *m = AcceptRequest{} }
+func (m *AcceptRequest) String() string { return proto.CompactTextString(m) }
+func (*AcceptRequest) ProtoMessage() {}
+
+const Default_AcceptRequest_TimeoutSeconds float64 = -1
+
+func (m *AcceptRequest) GetSocketDescriptor() string {
+ if m != nil && m.SocketDescriptor != nil {
+ return *m.SocketDescriptor
+ }
+ return ""
+}
+
+func (m *AcceptRequest) GetTimeoutSeconds() float64 {
+ if m != nil && m.TimeoutSeconds != nil {
+ return *m.TimeoutSeconds
+ }
+ return Default_AcceptRequest_TimeoutSeconds
+}
+
+type AcceptReply struct {
+ NewSocketDescriptor []byte `protobuf:"bytes,2,opt,name=new_socket_descriptor" json:"new_socket_descriptor,omitempty"`
+ RemoteAddress *AddressPort `protobuf:"bytes,3,opt,name=remote_address" json:"remote_address,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *AcceptReply) Reset() { *m = AcceptReply{} }
+func (m *AcceptReply) String() string { return proto.CompactTextString(m) }
+func (*AcceptReply) ProtoMessage() {}
+
+func (m *AcceptReply) GetNewSocketDescriptor() []byte {
+ if m != nil {
+ return m.NewSocketDescriptor
+ }
+ return nil
+}
+
+func (m *AcceptReply) GetRemoteAddress() *AddressPort {
+ if m != nil {
+ return m.RemoteAddress
+ }
+ return nil
+}
+
+type ShutDownRequest struct {
+ SocketDescriptor *string `protobuf:"bytes,1,req,name=socket_descriptor" json:"socket_descriptor,omitempty"`
+ How *ShutDownRequest_How `protobuf:"varint,2,req,name=how,enum=appengine.ShutDownRequest_How" json:"how,omitempty"`
+ SendOffset *int64 `protobuf:"varint,3,req,name=send_offset" json:"send_offset,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *ShutDownRequest) Reset() { *m = ShutDownRequest{} }
+func (m *ShutDownRequest) String() string { return proto.CompactTextString(m) }
+func (*ShutDownRequest) ProtoMessage() {}
+
+func (m *ShutDownRequest) GetSocketDescriptor() string {
+ if m != nil && m.SocketDescriptor != nil {
+ return *m.SocketDescriptor
+ }
+ return ""
+}
+
+func (m *ShutDownRequest) GetHow() ShutDownRequest_How {
+ if m != nil && m.How != nil {
+ return *m.How
+ }
+ return ShutDownRequest_SOCKET_SHUT_RD
+}
+
+func (m *ShutDownRequest) GetSendOffset() int64 {
+ if m != nil && m.SendOffset != nil {
+ return *m.SendOffset
+ }
+ return 0
+}
+
+type ShutDownReply struct {
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *ShutDownReply) Reset() { *m = ShutDownReply{} }
+func (m *ShutDownReply) String() string { return proto.CompactTextString(m) }
+func (*ShutDownReply) ProtoMessage() {}
+
+type CloseRequest struct {
+ SocketDescriptor *string `protobuf:"bytes,1,req,name=socket_descriptor" json:"socket_descriptor,omitempty"`
+ SendOffset *int64 `protobuf:"varint,2,opt,name=send_offset,def=-1" json:"send_offset,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *CloseRequest) Reset() { *m = CloseRequest{} }
+func (m *CloseRequest) String() string { return proto.CompactTextString(m) }
+func (*CloseRequest) ProtoMessage() {}
+
+const Default_CloseRequest_SendOffset int64 = -1
+
+func (m *CloseRequest) GetSocketDescriptor() string {
+ if m != nil && m.SocketDescriptor != nil {
+ return *m.SocketDescriptor
+ }
+ return ""
+}
+
+func (m *CloseRequest) GetSendOffset() int64 {
+ if m != nil && m.SendOffset != nil {
+ return *m.SendOffset
+ }
+ return Default_CloseRequest_SendOffset
+}
+
+type CloseReply struct {
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *CloseReply) Reset() { *m = CloseReply{} }
+func (m *CloseReply) String() string { return proto.CompactTextString(m) }
+func (*CloseReply) ProtoMessage() {}
+
+type SendRequest struct {
+ SocketDescriptor *string `protobuf:"bytes,1,req,name=socket_descriptor" json:"socket_descriptor,omitempty"`
+ Data []byte `protobuf:"bytes,2,req,name=data" json:"data,omitempty"`
+ StreamOffset *int64 `protobuf:"varint,3,req,name=stream_offset" json:"stream_offset,omitempty"`
+ Flags *int32 `protobuf:"varint,4,opt,name=flags,def=0" json:"flags,omitempty"`
+ SendTo *AddressPort `protobuf:"bytes,5,opt,name=send_to" json:"send_to,omitempty"`
+ TimeoutSeconds *float64 `protobuf:"fixed64,6,opt,name=timeout_seconds,def=-1" json:"timeout_seconds,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *SendRequest) Reset() { *m = SendRequest{} }
+func (m *SendRequest) String() string { return proto.CompactTextString(m) }
+func (*SendRequest) ProtoMessage() {}
+
+const Default_SendRequest_Flags int32 = 0
+const Default_SendRequest_TimeoutSeconds float64 = -1
+
+func (m *SendRequest) GetSocketDescriptor() string {
+ if m != nil && m.SocketDescriptor != nil {
+ return *m.SocketDescriptor
+ }
+ return ""
+}
+
+func (m *SendRequest) GetData() []byte {
+ if m != nil {
+ return m.Data
+ }
+ return nil
+}
+
+func (m *SendRequest) GetStreamOffset() int64 {
+ if m != nil && m.StreamOffset != nil {
+ return *m.StreamOffset
+ }
+ return 0
+}
+
+func (m *SendRequest) GetFlags() int32 {
+ if m != nil && m.Flags != nil {
+ return *m.Flags
+ }
+ return Default_SendRequest_Flags
+}
+
+func (m *SendRequest) GetSendTo() *AddressPort {
+ if m != nil {
+ return m.SendTo
+ }
+ return nil
+}
+
+func (m *SendRequest) GetTimeoutSeconds() float64 {
+ if m != nil && m.TimeoutSeconds != nil {
+ return *m.TimeoutSeconds
+ }
+ return Default_SendRequest_TimeoutSeconds
+}
+
+type SendReply struct {
+ DataSent *int32 `protobuf:"varint,1,opt,name=data_sent" json:"data_sent,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *SendReply) Reset() { *m = SendReply{} }
+func (m *SendReply) String() string { return proto.CompactTextString(m) }
+func (*SendReply) ProtoMessage() {}
+
+func (m *SendReply) GetDataSent() int32 {
+ if m != nil && m.DataSent != nil {
+ return *m.DataSent
+ }
+ return 0
+}
+
+type ReceiveRequest struct {
+ SocketDescriptor *string `protobuf:"bytes,1,req,name=socket_descriptor" json:"socket_descriptor,omitempty"`
+ DataSize *int32 `protobuf:"varint,2,req,name=data_size" json:"data_size,omitempty"`
+ Flags *int32 `protobuf:"varint,3,opt,name=flags,def=0" json:"flags,omitempty"`
+ TimeoutSeconds *float64 `protobuf:"fixed64,5,opt,name=timeout_seconds,def=-1" json:"timeout_seconds,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *ReceiveRequest) Reset() { *m = ReceiveRequest{} }
+func (m *ReceiveRequest) String() string { return proto.CompactTextString(m) }
+func (*ReceiveRequest) ProtoMessage() {}
+
+const Default_ReceiveRequest_Flags int32 = 0
+const Default_ReceiveRequest_TimeoutSeconds float64 = -1
+
+func (m *ReceiveRequest) GetSocketDescriptor() string {
+ if m != nil && m.SocketDescriptor != nil {
+ return *m.SocketDescriptor
+ }
+ return ""
+}
+
+func (m *ReceiveRequest) GetDataSize() int32 {
+ if m != nil && m.DataSize != nil {
+ return *m.DataSize
+ }
+ return 0
+}
+
+func (m *ReceiveRequest) GetFlags() int32 {
+ if m != nil && m.Flags != nil {
+ return *m.Flags
+ }
+ return Default_ReceiveRequest_Flags
+}
+
+func (m *ReceiveRequest) GetTimeoutSeconds() float64 {
+ if m != nil && m.TimeoutSeconds != nil {
+ return *m.TimeoutSeconds
+ }
+ return Default_ReceiveRequest_TimeoutSeconds
+}
+
+type ReceiveReply struct {
+ StreamOffset *int64 `protobuf:"varint,2,opt,name=stream_offset" json:"stream_offset,omitempty"`
+ Data []byte `protobuf:"bytes,3,opt,name=data" json:"data,omitempty"`
+ ReceivedFrom *AddressPort `protobuf:"bytes,4,opt,name=received_from" json:"received_from,omitempty"`
+ BufferSize *int32 `protobuf:"varint,5,opt,name=buffer_size" json:"buffer_size,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *ReceiveReply) Reset() { *m = ReceiveReply{} }
+func (m *ReceiveReply) String() string { return proto.CompactTextString(m) }
+func (*ReceiveReply) ProtoMessage() {}
+
+func (m *ReceiveReply) GetStreamOffset() int64 {
+ if m != nil && m.StreamOffset != nil {
+ return *m.StreamOffset
+ }
+ return 0
+}
+
+func (m *ReceiveReply) GetData() []byte {
+ if m != nil {
+ return m.Data
+ }
+ return nil
+}
+
+func (m *ReceiveReply) GetReceivedFrom() *AddressPort {
+ if m != nil {
+ return m.ReceivedFrom
+ }
+ return nil
+}
+
+func (m *ReceiveReply) GetBufferSize() int32 {
+ if m != nil && m.BufferSize != nil {
+ return *m.BufferSize
+ }
+ return 0
+}
+
+type PollEvent struct {
+ SocketDescriptor *string `protobuf:"bytes,1,req,name=socket_descriptor" json:"socket_descriptor,omitempty"`
+ RequestedEvents *int32 `protobuf:"varint,2,req,name=requested_events" json:"requested_events,omitempty"`
+ ObservedEvents *int32 `protobuf:"varint,3,req,name=observed_events" json:"observed_events,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *PollEvent) Reset() { *m = PollEvent{} }
+func (m *PollEvent) String() string { return proto.CompactTextString(m) }
+func (*PollEvent) ProtoMessage() {}
+
+func (m *PollEvent) GetSocketDescriptor() string {
+ if m != nil && m.SocketDescriptor != nil {
+ return *m.SocketDescriptor
+ }
+ return ""
+}
+
+func (m *PollEvent) GetRequestedEvents() int32 {
+ if m != nil && m.RequestedEvents != nil {
+ return *m.RequestedEvents
+ }
+ return 0
+}
+
+func (m *PollEvent) GetObservedEvents() int32 {
+ if m != nil && m.ObservedEvents != nil {
+ return *m.ObservedEvents
+ }
+ return 0
+}
+
+type PollRequest struct {
+ Events []*PollEvent `protobuf:"bytes,1,rep,name=events" json:"events,omitempty"`
+ TimeoutSeconds *float64 `protobuf:"fixed64,2,opt,name=timeout_seconds,def=-1" json:"timeout_seconds,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *PollRequest) Reset() { *m = PollRequest{} }
+func (m *PollRequest) String() string { return proto.CompactTextString(m) }
+func (*PollRequest) ProtoMessage() {}
+
+const Default_PollRequest_TimeoutSeconds float64 = -1
+
+func (m *PollRequest) GetEvents() []*PollEvent {
+ if m != nil {
+ return m.Events
+ }
+ return nil
+}
+
+func (m *PollRequest) GetTimeoutSeconds() float64 {
+ if m != nil && m.TimeoutSeconds != nil {
+ return *m.TimeoutSeconds
+ }
+ return Default_PollRequest_TimeoutSeconds
+}
+
+type PollReply struct {
+ Events []*PollEvent `protobuf:"bytes,2,rep,name=events" json:"events,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *PollReply) Reset() { *m = PollReply{} }
+func (m *PollReply) String() string { return proto.CompactTextString(m) }
+func (*PollReply) ProtoMessage() {}
+
+func (m *PollReply) GetEvents() []*PollEvent {
+ if m != nil {
+ return m.Events
+ }
+ return nil
+}
+
+type ResolveRequest struct {
+ Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
+ AddressFamilies []CreateSocketRequest_SocketFamily `protobuf:"varint,2,rep,name=address_families,enum=appengine.CreateSocketRequest_SocketFamily" json:"address_families,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *ResolveRequest) Reset() { *m = ResolveRequest{} }
+func (m *ResolveRequest) String() string { return proto.CompactTextString(m) }
+func (*ResolveRequest) ProtoMessage() {}
+
+func (m *ResolveRequest) GetName() string {
+ if m != nil && m.Name != nil {
+ return *m.Name
+ }
+ return ""
+}
+
+func (m *ResolveRequest) GetAddressFamilies() []CreateSocketRequest_SocketFamily {
+ if m != nil {
+ return m.AddressFamilies
+ }
+ return nil
+}
+
+type ResolveReply struct {
+ PackedAddress [][]byte `protobuf:"bytes,2,rep,name=packed_address" json:"packed_address,omitempty"`
+ CanonicalName *string `protobuf:"bytes,3,opt,name=canonical_name" json:"canonical_name,omitempty"`
+ Aliases []string `protobuf:"bytes,4,rep,name=aliases" json:"aliases,omitempty"`
+ XXX_unrecognized []byte `json:"-"`
+}
+
+func (m *ResolveReply) Reset() { *m = ResolveReply{} }
+func (m *ResolveReply) String() string { return proto.CompactTextString(m) }
+func (*ResolveReply) ProtoMessage() {}
+
+func (m *ResolveReply) GetPackedAddress() [][]byte {
+ if m != nil {
+ return m.PackedAddress
+ }
+ return nil
+}
+
+func (m *ResolveReply) GetCanonicalName() string {
+ if m != nil && m.CanonicalName != nil {
+ return *m.CanonicalName
+ }
+ return ""
+}
+
+func (m *ResolveReply) GetAliases() []string {
+ if m != nil {
+ return m.Aliases
+ }
+ return nil
+}
+
+func init() {
+}
diff --git a/go/src/google.golang.org/appengine/internal/socket/socket_service.proto b/go/src/google.golang.org/appengine/internal/socket/socket_service.proto
new file mode 100644
index 0000000..2fcc795
--- /dev/null
+++ b/go/src/google.golang.org/appengine/internal/socket/socket_service.proto
@@ -0,0 +1,460 @@
+syntax = "proto2";
+option go_package = "socket";
+
+package appengine;
+
+message RemoteSocketServiceError {
+ enum ErrorCode {
+ SYSTEM_ERROR = 1;
+ GAI_ERROR = 2;
+ FAILURE = 4;
+ PERMISSION_DENIED = 5;
+ INVALID_REQUEST = 6;
+ SOCKET_CLOSED = 7;
+ }
+
+ enum SystemError {
+ option allow_alias = true;
+
+ SYS_SUCCESS = 0;
+ SYS_EPERM = 1;
+ SYS_ENOENT = 2;
+ SYS_ESRCH = 3;
+ SYS_EINTR = 4;
+ SYS_EIO = 5;
+ SYS_ENXIO = 6;
+ SYS_E2BIG = 7;
+ SYS_ENOEXEC = 8;
+ SYS_EBADF = 9;
+ SYS_ECHILD = 10;
+ SYS_EAGAIN = 11;
+ SYS_EWOULDBLOCK = 11;
+ SYS_ENOMEM = 12;
+ SYS_EACCES = 13;
+ SYS_EFAULT = 14;
+ SYS_ENOTBLK = 15;
+ SYS_EBUSY = 16;
+ SYS_EEXIST = 17;
+ SYS_EXDEV = 18;
+ SYS_ENODEV = 19;
+ SYS_ENOTDIR = 20;
+ SYS_EISDIR = 21;
+ SYS_EINVAL = 22;
+ SYS_ENFILE = 23;
+ SYS_EMFILE = 24;
+ SYS_ENOTTY = 25;
+ SYS_ETXTBSY = 26;
+ SYS_EFBIG = 27;
+ SYS_ENOSPC = 28;
+ SYS_ESPIPE = 29;
+ SYS_EROFS = 30;
+ SYS_EMLINK = 31;
+ SYS_EPIPE = 32;
+ SYS_EDOM = 33;
+ SYS_ERANGE = 34;
+ SYS_EDEADLK = 35;
+ SYS_EDEADLOCK = 35;
+ SYS_ENAMETOOLONG = 36;
+ SYS_ENOLCK = 37;
+ SYS_ENOSYS = 38;
+ SYS_ENOTEMPTY = 39;
+ SYS_ELOOP = 40;
+ SYS_ENOMSG = 42;
+ SYS_EIDRM = 43;
+ SYS_ECHRNG = 44;
+ SYS_EL2NSYNC = 45;
+ SYS_EL3HLT = 46;
+ SYS_EL3RST = 47;
+ SYS_ELNRNG = 48;
+ SYS_EUNATCH = 49;
+ SYS_ENOCSI = 50;
+ SYS_EL2HLT = 51;
+ SYS_EBADE = 52;
+ SYS_EBADR = 53;
+ SYS_EXFULL = 54;
+ SYS_ENOANO = 55;
+ SYS_EBADRQC = 56;
+ SYS_EBADSLT = 57;
+ SYS_EBFONT = 59;
+ SYS_ENOSTR = 60;
+ SYS_ENODATA = 61;
+ SYS_ETIME = 62;
+ SYS_ENOSR = 63;
+ SYS_ENONET = 64;
+ SYS_ENOPKG = 65;
+ SYS_EREMOTE = 66;
+ SYS_ENOLINK = 67;
+ SYS_EADV = 68;
+ SYS_ESRMNT = 69;
+ SYS_ECOMM = 70;
+ SYS_EPROTO = 71;
+ SYS_EMULTIHOP = 72;
+ SYS_EDOTDOT = 73;
+ SYS_EBADMSG = 74;
+ SYS_EOVERFLOW = 75;
+ SYS_ENOTUNIQ = 76;
+ SYS_EBADFD = 77;
+ SYS_EREMCHG = 78;
+ SYS_ELIBACC = 79;
+ SYS_ELIBBAD = 80;
+ SYS_ELIBSCN = 81;
+ SYS_ELIBMAX = 82;
+ SYS_ELIBEXEC = 83;
+ SYS_EILSEQ = 84;
+ SYS_ERESTART = 85;
+ SYS_ESTRPIPE = 86;
+ SYS_EUSERS = 87;
+ SYS_ENOTSOCK = 88;
+ SYS_EDESTADDRREQ = 89;
+ SYS_EMSGSIZE = 90;
+ SYS_EPROTOTYPE = 91;
+ SYS_ENOPROTOOPT = 92;
+ SYS_EPROTONOSUPPORT = 93;
+ SYS_ESOCKTNOSUPPORT = 94;
+ SYS_EOPNOTSUPP = 95;
+ SYS_ENOTSUP = 95;
+ SYS_EPFNOSUPPORT = 96;
+ SYS_EAFNOSUPPORT = 97;
+ SYS_EADDRINUSE = 98;
+ SYS_EADDRNOTAVAIL = 99;
+ SYS_ENETDOWN = 100;
+ SYS_ENETUNREACH = 101;
+ SYS_ENETRESET = 102;
+ SYS_ECONNABORTED = 103;
+ SYS_ECONNRESET = 104;
+ SYS_ENOBUFS = 105;
+ SYS_EISCONN = 106;
+ SYS_ENOTCONN = 107;
+ SYS_ESHUTDOWN = 108;
+ SYS_ETOOMANYREFS = 109;
+ SYS_ETIMEDOUT = 110;
+ SYS_ECONNREFUSED = 111;
+ SYS_EHOSTDOWN = 112;
+ SYS_EHOSTUNREACH = 113;
+ SYS_EALREADY = 114;
+ SYS_EINPROGRESS = 115;
+ SYS_ESTALE = 116;
+ SYS_EUCLEAN = 117;
+ SYS_ENOTNAM = 118;
+ SYS_ENAVAIL = 119;
+ SYS_EISNAM = 120;
+ SYS_EREMOTEIO = 121;
+ SYS_EDQUOT = 122;
+ SYS_ENOMEDIUM = 123;
+ SYS_EMEDIUMTYPE = 124;
+ SYS_ECANCELED = 125;
+ SYS_ENOKEY = 126;
+ SYS_EKEYEXPIRED = 127;
+ SYS_EKEYREVOKED = 128;
+ SYS_EKEYREJECTED = 129;
+ SYS_EOWNERDEAD = 130;
+ SYS_ENOTRECOVERABLE = 131;
+ SYS_ERFKILL = 132;
+ }
+
+ optional int32 system_error = 1 [default=0];
+ optional string error_detail = 2;
+}
+
+message AddressPort {
+ required int32 port = 1;
+ optional bytes packed_address = 2;
+
+ optional string hostname_hint = 3;
+}
+
+
+
+message CreateSocketRequest {
+ enum SocketFamily {
+ IPv4 = 1;
+ IPv6 = 2;
+ }
+
+ enum SocketProtocol {
+ TCP = 1;
+ UDP = 2;
+ }
+
+ required SocketFamily family = 1;
+ required SocketProtocol protocol = 2;
+
+ repeated SocketOption socket_options = 3;
+
+ optional AddressPort proxy_external_ip = 4;
+
+ optional int32 listen_backlog = 5 [default=0];
+
+ optional AddressPort remote_ip = 6;
+
+ optional string app_id = 9;
+
+ optional int64 project_id = 10;
+}
+
+message CreateSocketReply {
+ optional string socket_descriptor = 1;
+
+ optional AddressPort server_address = 3;
+
+ optional AddressPort proxy_external_ip = 4;
+
+ extensions 1000 to max;
+}
+
+
+
+message BindRequest {
+ required string socket_descriptor = 1;
+ required AddressPort proxy_external_ip = 2;
+}
+
+message BindReply {
+ optional AddressPort proxy_external_ip = 1;
+}
+
+
+
+message GetSocketNameRequest {
+ required string socket_descriptor = 1;
+}
+
+message GetSocketNameReply {
+ optional AddressPort proxy_external_ip = 2;
+}
+
+
+
+message GetPeerNameRequest {
+ required string socket_descriptor = 1;
+}
+
+message GetPeerNameReply {
+ optional AddressPort peer_ip = 2;
+}
+
+
+message SocketOption {
+
+ enum SocketOptionLevel {
+ SOCKET_SOL_IP = 0;
+ SOCKET_SOL_SOCKET = 1;
+ SOCKET_SOL_TCP = 6;
+ SOCKET_SOL_UDP = 17;
+ }
+
+ enum SocketOptionName {
+ option allow_alias = true;
+
+ SOCKET_SO_DEBUG = 1;
+ SOCKET_SO_REUSEADDR = 2;
+ SOCKET_SO_TYPE = 3;
+ SOCKET_SO_ERROR = 4;
+ SOCKET_SO_DONTROUTE = 5;
+ SOCKET_SO_BROADCAST = 6;
+ SOCKET_SO_SNDBUF = 7;
+ SOCKET_SO_RCVBUF = 8;
+ SOCKET_SO_KEEPALIVE = 9;
+ SOCKET_SO_OOBINLINE = 10;
+ SOCKET_SO_LINGER = 13;
+ SOCKET_SO_RCVTIMEO = 20;
+ SOCKET_SO_SNDTIMEO = 21;
+
+ SOCKET_IP_TOS = 1;
+ SOCKET_IP_TTL = 2;
+ SOCKET_IP_HDRINCL = 3;
+ SOCKET_IP_OPTIONS = 4;
+
+ SOCKET_TCP_NODELAY = 1;
+ SOCKET_TCP_MAXSEG = 2;
+ SOCKET_TCP_CORK = 3;
+ SOCKET_TCP_KEEPIDLE = 4;
+ SOCKET_TCP_KEEPINTVL = 5;
+ SOCKET_TCP_KEEPCNT = 6;
+ SOCKET_TCP_SYNCNT = 7;
+ SOCKET_TCP_LINGER2 = 8;
+ SOCKET_TCP_DEFER_ACCEPT = 9;
+ SOCKET_TCP_WINDOW_CLAMP = 10;
+ SOCKET_TCP_INFO = 11;
+ SOCKET_TCP_QUICKACK = 12;
+ }
+
+ required SocketOptionLevel level = 1;
+ required SocketOptionName option = 2;
+ required bytes value = 3;
+}
+
+
+message SetSocketOptionsRequest {
+ required string socket_descriptor = 1;
+ repeated SocketOption options = 2;
+}
+
+message SetSocketOptionsReply {
+}
+
+message GetSocketOptionsRequest {
+ required string socket_descriptor = 1;
+ repeated SocketOption options = 2;
+}
+
+message GetSocketOptionsReply {
+ repeated SocketOption options = 2;
+}
+
+
+message ConnectRequest {
+ required string socket_descriptor = 1;
+ required AddressPort remote_ip = 2;
+ optional double timeout_seconds = 3 [default=-1];
+}
+
+message ConnectReply {
+ optional AddressPort proxy_external_ip = 1;
+
+ extensions 1000 to max;
+}
+
+
+message ListenRequest {
+ required string socket_descriptor = 1;
+ required int32 backlog = 2;
+}
+
+message ListenReply {
+}
+
+
+message AcceptRequest {
+ required string socket_descriptor = 1;
+ optional double timeout_seconds = 2 [default=-1];
+}
+
+message AcceptReply {
+ optional bytes new_socket_descriptor = 2;
+ optional AddressPort remote_address = 3;
+}
+
+
+
+message ShutDownRequest {
+ enum How {
+ SOCKET_SHUT_RD = 1;
+ SOCKET_SHUT_WR = 2;
+ SOCKET_SHUT_RDWR = 3;
+ }
+ required string socket_descriptor = 1;
+ required How how = 2;
+ required int64 send_offset = 3;
+}
+
+message ShutDownReply {
+}
+
+
+
+message CloseRequest {
+ required string socket_descriptor = 1;
+ optional int64 send_offset = 2 [default=-1];
+}
+
+message CloseReply {
+}
+
+
+
+message SendRequest {
+ required string socket_descriptor = 1;
+ required bytes data = 2 [ctype=CORD];
+ required int64 stream_offset = 3;
+ optional int32 flags = 4 [default=0];
+ optional AddressPort send_to = 5;
+ optional double timeout_seconds = 6 [default=-1];
+}
+
+message SendReply {
+ optional int32 data_sent = 1;
+}
+
+
+message ReceiveRequest {
+ enum Flags {
+ MSG_OOB = 1;
+ MSG_PEEK = 2;
+ }
+ required string socket_descriptor = 1;
+ required int32 data_size = 2;
+ optional int32 flags = 3 [default=0];
+ optional double timeout_seconds = 5 [default=-1];
+}
+
+message ReceiveReply {
+ optional int64 stream_offset = 2;
+ optional bytes data = 3 [ctype=CORD];
+ optional AddressPort received_from = 4;
+ optional int32 buffer_size = 5;
+}
+
+
+
+message PollEvent {
+
+ enum PollEventFlag {
+ SOCKET_POLLNONE = 0;
+ SOCKET_POLLIN = 1;
+ SOCKET_POLLPRI = 2;
+ SOCKET_POLLOUT = 4;
+ SOCKET_POLLERR = 8;
+ SOCKET_POLLHUP = 16;
+ SOCKET_POLLNVAL = 32;
+ SOCKET_POLLRDNORM = 64;
+ SOCKET_POLLRDBAND = 128;
+ SOCKET_POLLWRNORM = 256;
+ SOCKET_POLLWRBAND = 512;
+ SOCKET_POLLMSG = 1024;
+ SOCKET_POLLREMOVE = 4096;
+ SOCKET_POLLRDHUP = 8192;
+ };
+
+ required string socket_descriptor = 1;
+ required int32 requested_events = 2;
+ required int32 observed_events = 3;
+}
+
+message PollRequest {
+ repeated PollEvent events = 1;
+ optional double timeout_seconds = 2 [default=-1];
+}
+
+message PollReply {
+ repeated PollEvent events = 2;
+}
+
+message ResolveRequest {
+ required string name = 1;
+ repeated CreateSocketRequest.SocketFamily address_families = 2;
+}
+
+message ResolveReply {
+ enum ErrorCode {
+ SOCKET_EAI_ADDRFAMILY = 1;
+ SOCKET_EAI_AGAIN = 2;
+ SOCKET_EAI_BADFLAGS = 3;
+ SOCKET_EAI_FAIL = 4;
+ SOCKET_EAI_FAMILY = 5;
+ SOCKET_EAI_MEMORY = 6;
+ SOCKET_EAI_NODATA = 7;
+ SOCKET_EAI_NONAME = 8;
+ SOCKET_EAI_SERVICE = 9;
+ SOCKET_EAI_SOCKTYPE = 10;
+ SOCKET_EAI_SYSTEM = 11;
+ SOCKET_EAI_BADHINTS = 12;
+ SOCKET_EAI_PROTOCOL = 13;
+ SOCKET_EAI_OVERFLOW = 14;
+ SOCKET_EAI_MAX = 15;
+ };
+
+ repeated bytes packed_address = 2;
+ optional string canonical_name = 3;
+ repeated string aliases = 4;
+}
diff --git a/go/src/google.golang.org/appengine/internal/taskqueue/taskqueue_service.pb.go b/go/src/google.golang.org/appengine/internal/taskqueue/taskqueue_service.pb.go
index 9180ba6..6c9b90a 100644
--- a/go/src/google.golang.org/appengine/internal/taskqueue/taskqueue_service.pb.go
+++ b/go/src/google.golang.org/appengine/internal/taskqueue/taskqueue_service.pb.go
@@ -52,11 +52,13 @@
package taskqueue
import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
import math "math"
import appengine "google.golang.org/appengine/internal/datastore"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
+var _ = fmt.Errorf
var _ = math.Inf
type TaskQueueServiceError_ErrorCode int32
diff --git a/go/src/google.golang.org/appengine/internal/transaction.go b/go/src/google.golang.org/appengine/internal/transaction.go
index d8e063e..28a6d18 100644
--- a/go/src/google.golang.org/appengine/internal/transaction.go
+++ b/go/src/google.golang.org/appengine/internal/transaction.go
@@ -1,3 +1,7 @@
+// Copyright 2014 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
package internal
// This file implements hooks for applying datastore transactions.
diff --git a/go/src/google.golang.org/appengine/internal/urlfetch/urlfetch_service.pb.go b/go/src/google.golang.org/appengine/internal/urlfetch/urlfetch_service.pb.go
index e559522..5eff3c3 100644
--- a/go/src/google.golang.org/appengine/internal/urlfetch/urlfetch_service.pb.go
+++ b/go/src/google.golang.org/appengine/internal/urlfetch/urlfetch_service.pb.go
@@ -16,10 +16,12 @@
package urlfetch
import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
+var _ = fmt.Errorf
var _ = math.Inf
type URLFetchServiceError_ErrorCode int32
diff --git a/go/src/google.golang.org/appengine/internal/user/user_service.pb.go b/go/src/google.golang.org/appengine/internal/user/user_service.pb.go
index 67627d9..6b52ffc 100644
--- a/go/src/google.golang.org/appengine/internal/user/user_service.pb.go
+++ b/go/src/google.golang.org/appengine/internal/user/user_service.pb.go
@@ -22,10 +22,12 @@
package user
import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
+var _ = fmt.Errorf
var _ = math.Inf
type UserServiceError_ErrorCode int32
diff --git a/go/src/google.golang.org/appengine/internal/xmpp/xmpp_service.pb.go b/go/src/google.golang.org/appengine/internal/xmpp/xmpp_service.pb.go
index be1e986..6d5b0ae 100644
--- a/go/src/google.golang.org/appengine/internal/xmpp/xmpp_service.pb.go
+++ b/go/src/google.golang.org/appengine/internal/xmpp/xmpp_service.pb.go
@@ -24,10 +24,12 @@
package xmpp
import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
+var _ = fmt.Errorf
var _ = math.Inf
type XmppServiceError_ErrorCode int32
diff --git a/go/src/google.golang.org/appengine/log/log_test.go b/go/src/google.golang.org/appengine/log/log_test.go
index 5d7e985..726468e 100644
--- a/go/src/google.golang.org/appengine/log/log_test.go
+++ b/go/src/google.golang.org/appengine/log/log_test.go
@@ -1,3 +1,7 @@
+// Copyright 2014 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
package log
import (
diff --git a/go/src/google.golang.org/appengine/memcache/memcache.go b/go/src/google.golang.org/appengine/memcache/memcache.go
index f91331f..d8eed4b 100644
--- a/go/src/google.golang.org/appengine/memcache/memcache.go
+++ b/go/src/google.golang.org/appengine/memcache/memcache.go
@@ -196,7 +196,7 @@
// by delta and returns the new value. The value must fit in a uint64.
// Overflow wraps around, and underflow is capped to zero. The
// provided delta may be negative. If the key doesn't exist in
-// memcacheg, the provided initial value is used to atomically
+// memcache, the provided initial value is used to atomically
// populate it before the delta is applied.
// The key must be at most 250 bytes in length.
func Increment(c context.Context, key string, delta int64, initialValue uint64) (newValue uint64, err error) {
diff --git a/go/src/google.golang.org/appengine/memcache/memcache_test.go b/go/src/google.golang.org/appengine/memcache/memcache_test.go
index 70e3830..5f93954 100644
--- a/go/src/google.golang.org/appengine/memcache/memcache_test.go
+++ b/go/src/google.golang.org/appengine/memcache/memcache_test.go
@@ -1,3 +1,7 @@
+// Copyright 2014 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
package memcache
import (
diff --git a/go/src/google.golang.org/appengine/namespace.go b/go/src/google.golang.org/appengine/namespace.go
index 9c00e90..65b0a00 100644
--- a/go/src/google.golang.org/appengine/namespace.go
+++ b/go/src/google.golang.org/appengine/namespace.go
@@ -20,7 +20,6 @@
return nil, fmt.Errorf("appengine: namespace %q does not match /%s/", namespace, validNamespace)
}
n := &namespacedContext{
- ctx: c,
namespace: namespace,
}
return internal.WithNamespace(internal.WithCallOverride(c, n.call), namespace), nil
@@ -31,14 +30,13 @@
// namespacedContext wraps a Context to support namespaces.
type namespacedContext struct {
- ctx context.Context
namespace string
}
-func (n *namespacedContext) call(_ context.Context, service, method string, in, out proto.Message) error {
+func (n *namespacedContext) call(ctx context.Context, service, method string, in, out proto.Message) error {
// Apply any namespace mods.
if mod, ok := internal.NamespaceMods[service]; ok {
mod(in, n.namespace)
}
- return internal.Call(n.ctx, service, method, in, out)
+ return internal.Call(ctx, service, method, in, out)
}
diff --git a/go/src/google.golang.org/appengine/namespace_test.go b/go/src/google.golang.org/appengine/namespace_test.go
index 92fa534..dba45a3 100644
--- a/go/src/google.golang.org/appengine/namespace_test.go
+++ b/go/src/google.golang.org/appengine/namespace_test.go
@@ -1,7 +1,19 @@
+// Copyright 2014 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
package appengine
import (
+ "strings"
"testing"
+
+ "github.com/golang/protobuf/proto"
+ "golang.org/x/net/context"
+
+ "google.golang.org/appengine/internal"
+ "google.golang.org/appengine/internal/aetesting"
+ basepb "google.golang.org/appengine/internal/base"
)
func TestNamespaceValidity(t *testing.T) {
@@ -23,7 +35,7 @@
{" ", false},
}
for _, tc := range testCases {
- _, err := Namespace(nil, tc.namespace)
+ _, err := Namespace(context.Background(), tc.namespace)
if err == nil && !tc.ok {
t.Errorf("Namespace %q should be rejected, but wasn't", tc.namespace)
} else if err != nil && tc.ok {
@@ -31,3 +43,49 @@
}
}
}
+
+func TestNamespaceApplication(t *testing.T) {
+ internal.NamespaceMods["srv"] = func(m proto.Message, namespace string) {
+ sm := m.(*basepb.StringProto)
+ if strings.Contains(sm.GetValue(), "-") {
+ // be idempotent
+ return
+ }
+ sm.Value = proto.String(sm.GetValue() + "-" + namespace)
+ }
+ ctx := aetesting.FakeSingleContext(t, "srv", "mth", func(in, out *basepb.StringProto) error {
+ out.Value = proto.String(in.GetValue())
+ return nil
+ })
+ call := func(ctx context.Context, in string) (out string, ok bool) {
+ inm := &basepb.StringProto{Value: &in}
+ outm := &basepb.StringProto{}
+ if err := internal.Call(ctx, "srv", "mth", inm, outm); err != nil {
+ t.Errorf("RPC(in=%q) failed: %v", in, err)
+ return "", false
+ }
+ return outm.GetValue(), true
+ }
+
+ // Check without a namespace.
+ got, ok := call(ctx, "foo")
+ if !ok {
+ t.FailNow()
+ }
+ if got != "foo" {
+ t.Errorf("Un-namespaced RPC returned %q, want %q", got, "foo")
+ }
+
+ // Now check by applying a namespace.
+ nsCtx, err := Namespace(ctx, "myns")
+ if err != nil {
+ t.Fatal(err)
+ }
+ got, ok = call(nsCtx, "bar")
+ if !ok {
+ t.FailNow()
+ }
+ if got != "bar-myns" {
+ t.Errorf("Namespaced RPC returned %q, want %q", got, "bar-myns")
+ }
+}
diff --git a/go/src/google.golang.org/appengine/remote_api/client_test.go b/go/src/google.golang.org/appengine/remote_api/client_test.go
index 4a49cbe..2e892a0 100644
--- a/go/src/google.golang.org/appengine/remote_api/client_test.go
+++ b/go/src/google.golang.org/appengine/remote_api/client_test.go
@@ -1,3 +1,7 @@
+// Copyright 2014 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
package remote_api
import (
diff --git a/go/src/google.golang.org/appengine/search/doc.go b/go/src/google.golang.org/appengine/search/doc.go
new file mode 100644
index 0000000..ef56415
--- /dev/null
+++ b/go/src/google.golang.org/appengine/search/doc.go
@@ -0,0 +1,205 @@
+// Copyright 2015 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+/*
+Package search provides a client for App Engine's search service.
+
+
+Basic Operations
+
+Indexes contain documents. Each index is identified by its name: a
+human-readable ASCII string.
+
+Within an index, documents are associated with an ID, which is also
+a human-readable ASCII string. A document's contents are a mapping from
+case-sensitive field names to values. Valid types for field values are:
+ - string,
+ - search.Atom,
+ - search.HTML,
+ - time.Time (stored with millisecond precision),
+ - float64 (value between -2,147,483,647 and 2,147,483,647 inclusive),
+ - appengine.GeoPoint.
+
+The Get and Put methods on an Index load and save a document.
+A document's contents are typically represented by a struct pointer.
+
+Example code:
+
+ type Doc struct {
+ Author string
+ Comment string
+ Creation time.Time
+ }
+
+ index, err := search.Open("comments")
+ if err != nil {
+ return err
+ }
+ newID, err := index.Put(ctx, "", &Doc{
+ Author: "gopher",
+ Comment: "the truth of the matter",
+ Creation: time.Now(),
+ })
+ if err != nil {
+ return err
+ }
+
+A single document can be retrieved by its ID. Pass a destination struct
+to Get to hold the resulting document.
+
+ var doc Doc
+ err := index.Get(ctx, id, &doc)
+ if err != nil {
+ return err
+ }
+
+
+Search and Listing Documents
+
+Indexes have two methods for retrieving multiple documents at once: Search and
+List.
+
+Searching an index for a query will result in an iterator. As with an iterator
+from package datastore, pass a destination struct to Next to decode the next
+result. Next will return Done when the iterator is exhausted.
+
+ for t := index.Search(ctx, "Comment:truth", nil); ; {
+ var doc Doc
+ id, err := t.Next(&doc)
+ if err == search.Done {
+ break
+ }
+ if err != nil {
+ return err
+ }
+ fmt.Fprintf(w, "%s -> %#v\n", id, doc)
+ }
+
+Search takes a string query to determine which documents to return. The query
+can be simple, such as a single word to match, or complex. The query
+language is described at
+https://cloud.google.com/appengine/docs/go/search/query_strings
+
+Search also takes an optional SearchOptions struct which gives much more
+control over how results are calculated and returned.
+
+Call List to iterate over all documents in an index.
+
+ for t := index.List(ctx, nil); ; {
+ var doc Doc
+ id, err := t.Next(&doc)
+ if err == search.Done {
+ break
+ }
+ if err != nil {
+ return err
+ }
+ fmt.Fprintf(w, "%s -> %#v\n", id, doc)
+ }
+
+
+Fields and Facets
+
+A document's contents can be represented by a variety of types. These are
+typically struct pointers, but they can also be represented by any type
+implementing the FieldLoadSaver interface. The FieldLoadSaver allows metadata
+to be set for the document with the DocumentMetadata type. Struct pointers are
+more strongly typed and are easier to use; FieldLoadSavers are more flexible.
+
+A document's contents can be expressed in two ways: fields and facets.
+
+Fields are the most common way of providing content for documents. Fields can
+store data in multiple types and can be matched in searches using query
+strings.
+
+Facets provide a way to attach categorical information to a document. The only
+valid types for facets are search.Atom and float64. Facets allow search
+results to contain summaries of the categories matched in a search, and to
+restrict searches to only match against specific categories.
+
+By default, for struct pointers, all of the struct fields are used as document
+fields, and the field name used is the same as on the struct (and hence must
+start with an upper case letter). Struct fields may have a
+`search:"name,options"` tag. The name must start with a letter and be
+composed only of word characters. If options is "facet" then the struct
+field will be used as a document facet. If options is "" then the comma
+may be omitted. There are no other recognized options.
+
+Example code:
+
+ // A and B are renamed to a and b.
+ // A, C and I are facets.
+ // D's tag is equivalent to having no tag at all (E).
+ // I has tag information for both the search and json packages.
+ type TaggedStruct struct {
+ A float64 `search:"a,facet"`
+ B float64 `search:"b"`
+ C float64 `search:",facet"`
+ D float64 `search:""`
+ E float64
+ I float64 `search:",facet" json:"i"`
+ }
+
+
+The FieldLoadSaver Interface
+
+A document's contents can also be represented by any type that implements the
+FieldLoadSaver interface. This type may be a struct pointer, but it
+does not have to be. The search package will call Load when loading the
+document's contents, and Save when saving them. In addition to a slice of
+Fields, the Load and Save methods also use the DocumentMetadata type to
+provide additional information about a document (such as its Rank, or set of
+Facets). Possible uses for this interface include deriving non-stored fields,
+verifying fields or setting specific languages for string and HTML fields.
+
+Example code:
+
+ type CustomFieldsExample struct {
+ // Item's title and which language it is in.
+ Title string
+ Lang string
+ // Mass, in grams.
+ Mass int
+ }
+
+ func (x *CustomFieldsExample) Load(fields []search.Field, meta *search.DocumentMetadata) error {
+ // Load the title field, failing if any other field is found.
+ for _, f := range fields {
+ if f.Name != "title" {
+ return fmt.Errorf("unknown field %q", f.Name)
+ }
+ s, ok := f.Value.(string)
+ if !ok {
+ return fmt.Errorf("unsupported type %T for field %q", f.Value, f.Name)
+ }
+ x.Title = s
+ x.Lang = f.Language
+ }
+ // Load the mass facet, failing if any other facet is found.
+ for _, f := range meta.Facets {
+ if f.Name != "mass" {
+ return fmt.Errorf("unknown facet %q", f.Name)
+ }
+ m, ok := f.Value.(float64)
+ if !ok {
+ return fmt.Errorf("unsupported type %T for facet %q", f.Value, f.Name)
+ }
+ x.Mass = int(m)
+ }
+ return nil
+ }
+
+ func (x *CustomFieldsExample) Save() ([]search.Field, *search.DocumentMetadata, error) {
+ fields := []search.Field{
+ {Name: "title", Value: x.Title, Language: x.Lang},
+ }
+ meta := &search.DocumentMetdata{
+ Facets: {
+ {Name: "mass", Value: float64(x.Mass)},
+ },
+ }
+ return fields, meta, nil
+ }
+*/
+package search
diff --git a/go/src/google.golang.org/appengine/search/field.go b/go/src/google.golang.org/appengine/search/field.go
index b4c31c0..e2f273d 100644
--- a/go/src/google.golang.org/appengine/search/field.go
+++ b/go/src/google.golang.org/appengine/search/field.go
@@ -4,15 +4,10 @@
package search
-import (
- "fmt"
- "reflect"
-)
-
// Field is a name/value pair. A search index's document can be loaded and
// saved as a sequence of Fields.
type Field struct {
- // Name is the field name.
+ // Name is the field name. A valid field name matches /[A-Za-z][A-Za-z0-9_]*/.
Name string
// Value is the field value. The valid types are:
// - string,
@@ -32,12 +27,34 @@
Derived bool
}
+// Facet is a name/value pair which is used to add categorical information to a
+// document.
+type Facet struct {
+ // Name is the facet name. A valid facet name matches /[A-Za-z][A-Za-z0-9_]*/.
+ // A facet name cannot be longer than 500 characters.
+ Name string
+ // Value is the facet value.
+ //
+ // When being used in documents (for example, in
+ // DocumentMetadata.Facets), the valid types are:
+ // - search.Atom,
+ // - float64.
+ //
+ // When being used in SearchOptions.Refinements or being returned
+ // in FacetResult, the valid types are:
+ // - search.Atom,
+ // - search.Range.
+ Value interface{}
+}
+
// DocumentMetadata is a struct containing information describing a given document.
type DocumentMetadata struct {
// Rank is an integer specifying the order the document will be returned in
// search results. If zero, the rank will be set to the number of seconds since
// 2011-01-01 00:00:00 UTC when being Put into an index.
Rank int
+ // Facets is the set of facets for this document.
+ Facets []Facet
}
// FieldLoadSaver can be converted from and to a slice of Fields
@@ -63,82 +80,3 @@
}
var _ FieldLoadSaver = (*FieldList)(nil)
-
-// structFLS adapts a struct to be a FieldLoadSaver.
-type structFLS struct {
- reflect.Value
-}
-
-func (s structFLS) Load(fields []Field, _ *DocumentMetadata) (err error) {
- for _, field := range fields {
- f := s.FieldByName(field.Name)
- if !f.IsValid() {
- err = &ErrFieldMismatch{
- FieldName: field.Name,
- Reason: "no such struct field",
- }
- continue
- }
- if !f.CanSet() {
- err = &ErrFieldMismatch{
- FieldName: field.Name,
- Reason: "cannot set struct field",
- }
- continue
- }
- v := reflect.ValueOf(field.Value)
- if ft, vt := f.Type(), v.Type(); ft != vt {
- err = &ErrFieldMismatch{
- FieldName: field.Name,
- Reason: fmt.Sprintf("type mismatch: %v for %v data", ft, vt),
- }
- continue
- }
- f.Set(v)
- }
- return err
-}
-
-func (s structFLS) Save() ([]Field, *DocumentMetadata, error) {
- fields := make([]Field, 0, s.NumField())
- for i := 0; i < s.NumField(); i++ {
- f := s.Field(i)
- if !f.CanSet() {
- continue
- }
- fields = append(fields, Field{
- Name: s.Type().Field(i).Name,
- Value: f.Interface(),
- })
- }
- return fields, nil, nil
-}
-
-// newStructFLS returns a FieldLoadSaver for the struct pointer p.
-func newStructFLS(p interface{}) (FieldLoadSaver, error) {
- v := reflect.ValueOf(p)
- if v.Kind() != reflect.Ptr || v.IsNil() || v.Elem().Kind() != reflect.Struct {
- return nil, ErrInvalidDocumentType
- }
- return structFLS{v.Elem()}, nil
-}
-
-// LoadStruct loads the fields from f to dst. dst must be a struct pointer.
-func LoadStruct(dst interface{}, f []Field) error {
- x, err := newStructFLS(dst)
- if err != nil {
- return err
- }
- return x.Load(f, nil)
-}
-
-// SaveStruct returns the fields from src as a slice of Field.
-// src must be a struct pointer.
-func SaveStruct(src interface{}) ([]Field, error) {
- x, err := newStructFLS(src)
- if err != nil {
- return nil, err
- }
- fs, _, err := x.Save()
- return fs, err
-}
diff --git a/go/src/google.golang.org/appengine/search/search.go b/go/src/google.golang.org/appengine/search/search.go
index fede7dd..cbba874 100644
--- a/go/src/google.golang.org/appengine/search/search.go
+++ b/go/src/google.golang.org/appengine/search/search.go
@@ -2,89 +2,6 @@
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
-/*
-Package search provides a client for App Engine's search service.
-
-Indexes contains documents, and a document's contents are a mapping from case-
-sensitive field names to values. In Go, documents are represented by struct
-pointers, and the valid types for a struct's fields are:
- - string,
- - search.Atom,
- - search.HTML,
- - time.Time (stored with millisecond precision),
- - float64 (value between -2,147,483,647 and 2,147,483,647 inclusive),
- - appengine.GeoPoint.
-
-Documents can also be represented by any type implementing the FieldLoadSaver
-interface.
-
-Example code:
-
- type Doc struct {
- Author string
- Comment string
- Creation time.Time
- }
-
- index, err := search.Open("comments")
- if err != nil {
- return err
- }
- newID, err := index.Put(c, "", &Doc{
- Author: "gopher",
- Comment: "the truth of the matter",
- Creation: time.Now(),
- })
- if err != nil {
- return err
- }
-
-Searching an index for a query will result in an iterator. As with an iterator
-from package datastore, pass a destination struct to Next to decode the next
-result. Next will return Done when the iterator is exhausted.
-
- for t := index.Search(c, "Comment:truth", nil); ; {
- var doc Doc
- id, err := t.Next(&doc)
- if err == search.Done {
- break
- }
- if err != nil {
- return err
- }
- fmt.Fprintf(w, "%s -> %#v\n", id, doc)
- }
-
-Call List to iterate over documents.
-
- for t := index.List(c, nil); ; {
- var doc Doc
- id, err := t.Next(&doc)
- if err == search.Done {
- break
- }
- if err != nil {
- return err
- }
- fmt.Fprintf(w, "%s -> %#v\n", id, doc)
- }
-
-A single document can also be retrieved by its ID. Pass a destination struct
-to Get to hold the resulting document.
-
- var doc Doc
- err := index.Get(c, id, &doc)
- if err != nil {
- return err
- }
-
-Queries are expressed as strings, plus some optional parameters. The query
-language is described at
-https://cloud.google.com/appengine/docs/go/search/query_strings
-
-Note that in Go, field names come from the struct field definition and begin
-with an upper case letter.
-*/
package search // import "google.golang.org/appengine/search"
// TODO: let Put specify the document language: "en", "fr", etc. Also: order_id?? storage??
@@ -96,6 +13,7 @@
import (
"errors"
"fmt"
+ "math"
"reflect"
"regexp"
"strconv"
@@ -120,18 +38,6 @@
ErrNoSuchDocument = errors.New("search: no such document")
)
-// ErrFieldMismatch is returned when a field is to be loaded into a different
-// than the one it was stored from, or when a field is missing or unexported in
-// the destination struct.
-type ErrFieldMismatch struct {
- FieldName string
- Reason string
-}
-
-func (e *ErrFieldMismatch) Error() string {
- return fmt.Sprintf("search: cannot load field %q: %s", e.FieldName, e.Reason)
-}
-
// Atom is a document field whose contents are indexed as a single indivisible
// string.
type Atom string
@@ -155,11 +61,12 @@
}
var (
- fieldNameRE = regexp.MustCompile(`^[A-Z][A-Za-z0-9_]*$`)
+ fieldNameRE = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_]*$`)
languageRE = regexp.MustCompile(`^[a-z]{2}$`)
)
-// validFieldName is the Go equivalent of Python's _CheckFieldName.
+// validFieldName is the Go equivalent of Python's _CheckFieldName. It checks
+// the validity of both field and facet names.
func validFieldName(s string) bool {
return len(s) <= 500 && fieldNameRE.MatchString(s)
}
@@ -213,22 +120,10 @@
// src must be a non-nil struct pointer or implement the FieldLoadSaver
// interface.
func (x *Index) Put(c context.Context, id string, src interface{}) (string, error) {
- fields, meta, err := saveDoc(src)
+ d, err := saveDoc(src)
if err != nil {
return "", err
}
- d := &pb.Document{
- Field: fields,
- OrderId: proto.Int32(int32(time.Since(orderIDEpoch).Seconds())),
- }
- if meta != nil {
- if meta.Rank != 0 {
- if !validDocRank(meta.Rank) {
- return "", fmt.Errorf("search: invalid rank %d, must be [0, 2^31)", meta.Rank)
- }
- *d.OrderId = int32(meta.Rank)
- }
- }
if id != "" {
if !validIndexNameOrDocID(id) {
return "", fmt.Errorf("search: invalid ID %q", id)
@@ -291,10 +186,7 @@
if len(res.Document) != 1 || res.Document[0].GetId() != id {
return ErrNoSuchDocument
}
- metadata := &DocumentMetadata{
- Rank: int(res.Document[0].GetOrderId()),
- }
- return loadDoc(dst, res.Document[0].Field, nil, metadata)
+ return loadDoc(dst, res.Document[0], nil)
}
// Delete deletes a document from the index.
@@ -327,13 +219,10 @@
count: -1,
listInclusive: true,
more: moreList,
- limit: -1,
}
if opts != nil {
t.listStartID = opts.StartID
- if opts.Limit > 0 {
- t.limit = opts.Limit
- }
+ t.limit = opts.Limit
t.idsOnly = opts.IDsOnly
}
return t
@@ -365,7 +254,7 @@
}
t.listRes = res.Document
t.listStartID, t.listInclusive, t.more = "", false, nil
- if len(res.Document) != 0 {
+ if len(res.Document) != 0 && t.limit <= 0 {
if id := res.Document[len(res.Document)-1].GetId(); id != "" {
t.listStartID, t.more = id, moreList
}
@@ -396,26 +285,37 @@
index: x,
searchQuery: query,
more: moreSearch,
- limit: -1,
}
if opts != nil {
- if opts.Limit > 0 {
- t.limit = opts.Limit
+ if opts.Cursor != "" {
+ if opts.Offset != 0 {
+ return errIter("at most one of Cursor and Offset may be specified")
+ }
+ t.searchCursor = proto.String(string(opts.Cursor))
}
+ t.limit = opts.Limit
t.fields = opts.Fields
t.idsOnly = opts.IDsOnly
t.sort = opts.Sort
t.exprs = opts.Expressions
+ t.refinements = opts.Refinements
+ t.facetOpts = opts.Facets
+ t.searchOffset = opts.Offset
}
return t
}
func moreSearch(t *Iterator) error {
+ // We use per-result (rather than single/per-page) cursors since this
+ // lets us return a Cursor for every iterator document. The two cursor
+ // types are largely interchangeable: a page cursor is the same as the
+ // last per-result cursor in a given search response.
req := &pb.SearchRequest{
Params: &pb.SearchParams{
IndexSpec: &t.index.spec,
Query: &t.searchQuery,
- CursorType: pb.SearchParams_SINGLE.Enum(),
+ Cursor: t.searchCursor,
+ CursorType: pb.SearchParams_PER_RESULT.Enum(),
FieldSpec: &pb.FieldSpec{
Name: t.fields,
},
@@ -424,6 +324,10 @@
if t.limit > 0 {
req.Params.Limit = proto.Int32(int32(t.limit))
}
+ if t.searchOffset > 0 {
+ req.Params.Offset = proto.Int32(int32(t.searchOffset))
+ t.searchOffset = 0
+ }
if t.idsOnly {
req.Params.KeysOnly = &t.idsOnly
}
@@ -432,16 +336,25 @@
return err
}
}
+ if t.refinements != nil {
+ if err := refinementsToProto(t.refinements, req.Params); err != nil {
+ return err
+ }
+ }
for _, e := range t.exprs {
req.Params.FieldSpec.Expression = append(req.Params.FieldSpec.Expression, &pb.FieldSpec_Expression{
Name: proto.String(e.Name),
Expression: proto.String(e.Expr),
})
}
-
- if t.searchCursor != nil {
- req.Params.Cursor = t.searchCursor
+ for _, f := range t.facetOpts {
+ if err := f.setParams(req.Params); err != nil {
+ return fmt.Errorf("bad FacetSearchOption: %v", err)
+ }
}
+ // Don't repeat facet search.
+ t.facetOpts = nil
+
res := &pb.SearchResponse{}
if err := internal.Call(t.c, "search", "Search", req, res); err != nil {
return err
@@ -450,11 +363,14 @@
return fmt.Errorf("search: %s: %s", res.Status.GetCode(), res.Status.GetErrorDetail())
}
t.searchRes = res.Result
+ if len(res.FacetResult) > 0 {
+ t.facetRes = res.FacetResult
+ }
t.count = int(*res.MatchedCount)
- if res.Cursor != nil {
- t.searchCursor, t.more = res.Cursor, moreSearch
+ if t.limit > 0 {
+ t.more = nil
} else {
- t.searchCursor, t.more = nil, nil
+ t.more = moreSearch
}
return nil
}
@@ -481,9 +397,30 @@
// document.
Expressions []FieldExpression
- // TODO: cursor, offset, maybe others.
+ // Facets controls what facet information is returned for these search results.
+ // If no options are specified, no facet results will be returned.
+ Facets []FacetSearchOption
+
+ // Refinements filters the returned documents by requiring them to contain facets
+ // with specific values. Refinements are applied in conjunction for facets with
+ // different names, and in disjunction otherwise.
+ Refinements []Facet
+
+ // Cursor causes the results to commence with the first document after
+ // the document associated with the cursor.
+ Cursor Cursor
+
+ // Offset specifies the number of documents to skip over before returning results.
+ // When specified, Cursor must be nil.
+ Offset int
}
+// Cursor represents an iterator's position.
+//
+// The string value of a cursor is web-safe. It can be saved and restored
+// for later use.
+type Cursor string
+
// FieldExpression defines a custom expression to evaluate for each result.
type FieldExpression struct {
// Name is the name to use for the computed field.
@@ -495,6 +432,130 @@
Expr string
}
+// FacetSearchOption controls what facet information is returned in search results.
+type FacetSearchOption interface {
+ setParams(*pb.SearchParams) error
+}
+
+// AutoFacetDiscovery returns a FacetSearchOption which enables automatic facet
+// discovery for the search. Automatic facet discovery looks for the facets
+// which appear the most often in the aggregate in the matched documents.
+//
+// The maximum number of facets returned is controlled by facetLimit, and the
+// maximum number of values per facet by facetLimit. A limit of zero indicates
+// a default limit should be used.
+func AutoFacetDiscovery(facetLimit, valueLimit int) FacetSearchOption {
+ return &autoFacetOpt{facetLimit, valueLimit}
+}
+
+type autoFacetOpt struct {
+ facetLimit, valueLimit int
+}
+
+const defaultAutoFacetLimit = 10 // As per python runtime search.py.
+
+func (o *autoFacetOpt) setParams(params *pb.SearchParams) error {
+ lim := int32(o.facetLimit)
+ if lim == 0 {
+ lim = defaultAutoFacetLimit
+ }
+ params.AutoDiscoverFacetCount = &lim
+ if o.valueLimit > 0 {
+ params.FacetAutoDetectParam = &pb.FacetAutoDetectParam{
+ ValueLimit: proto.Int32(int32(o.valueLimit)),
+ }
+ }
+ return nil
+}
+
+// FacetDiscovery returns a FacetSearchOption which selects a facet to be
+// returned with the search results. By default, the most frequently
+// occurring values for that facet will be returned. However, you can also
+// specify a list of particular Atoms or specific Ranges to return.
+func FacetDiscovery(name string, value ...interface{}) FacetSearchOption {
+ return &facetOpt{name, value}
+}
+
+type facetOpt struct {
+ name string
+ values []interface{}
+}
+
+func (o *facetOpt) setParams(params *pb.SearchParams) error {
+ req := &pb.FacetRequest{Name: &o.name}
+ params.IncludeFacet = append(params.IncludeFacet, req)
+ if len(o.values) == 0 {
+ return nil
+ }
+ vtype := reflect.TypeOf(o.values[0])
+ reqParam := &pb.FacetRequestParam{}
+ for _, v := range o.values {
+ if reflect.TypeOf(v) != vtype {
+ return errors.New("values must all be Atom, or must all be Range")
+ }
+ switch v := v.(type) {
+ case Atom:
+ reqParam.ValueConstraint = append(reqParam.ValueConstraint, string(v))
+ case Range:
+ rng, err := rangeToProto(v)
+ if err != nil {
+ return fmt.Errorf("invalid range: %v", err)
+ }
+ reqParam.Range = append(reqParam.Range, rng)
+ default:
+ return fmt.Errorf("unsupported value type %T", v)
+ }
+ }
+ req.Params = reqParam
+ return nil
+}
+
+// FacetDocumentDepth returns a FacetSearchOption which controls the number of
+// documents to be evaluated with preparing facet results.
+func FacetDocumentDepth(depth int) FacetSearchOption {
+ return facetDepthOpt(depth)
+}
+
+type facetDepthOpt int
+
+func (o facetDepthOpt) setParams(params *pb.SearchParams) error {
+ params.FacetDepth = proto.Int32(int32(o))
+ return nil
+}
+
+// FacetResult represents the number of times a particular facet and value
+// appeared in the documents matching a search request.
+type FacetResult struct {
+ Facet
+
+ // Count is the number of times this specific facet and value appeared in the
+ // matching documents.
+ Count int
+}
+
+// Range represents a numeric range with inclusive start and exclusive end.
+// Start may be specified as math.Inf(-1) to indicate there is no minimum
+// value, and End may similarly be specified as math.Inf(1); at least one of
+// Start or End must be a finite number.
+type Range struct {
+ Start, End float64
+}
+
+var (
+ negInf = math.Inf(-1)
+ posInf = math.Inf(1)
+)
+
+// AtLeast returns a Range matching any value greater than, or equal to, min.
+func AtLeast(min float64) Range {
+ return Range{Start: min, End: posInf}
+}
+
+// LessThan returns a Range matching any value less than max.
+func LessThan(max float64) Range {
+ return Range{Start: negInf, End: max}
+}
+
// SortOptions control the ordering and scoring of search results.
type SortOptions struct {
// Expressions is a slice of expressions representing a multi-dimensional
@@ -586,6 +647,59 @@
return nil
}
+func refinementsToProto(refinements []Facet, params *pb.SearchParams) error {
+ for _, r := range refinements {
+ ref := &pb.FacetRefinement{
+ Name: proto.String(r.Name),
+ }
+ switch v := r.Value.(type) {
+ case Atom:
+ ref.Value = proto.String(string(v))
+ case Range:
+ rng, err := rangeToProto(v)
+ if err != nil {
+ return fmt.Errorf("search: refinement for facet %q: %v", r.Name, err)
+ }
+ // Unfortunately there are two identical messages for identify Facet ranges.
+ ref.Range = &pb.FacetRefinement_Range{Start: rng.Start, End: rng.End}
+ default:
+ return fmt.Errorf("search: unsupported refinement for facet %q of type %T", r.Name, v)
+ }
+ params.FacetRefinement = append(params.FacetRefinement, ref)
+ }
+ return nil
+}
+
+func rangeToProto(r Range) (*pb.FacetRange, error) {
+ rng := &pb.FacetRange{}
+ if r.Start != negInf {
+ if !validFloat(r.Start) {
+ return nil, errors.New("invalid value for Start")
+ }
+ rng.Start = proto.String(strconv.FormatFloat(r.Start, 'e', -1, 64))
+ } else if r.End == posInf {
+ return nil, errors.New("either Start or End must be finite")
+ }
+ if r.End != posInf {
+ if !validFloat(r.End) {
+ return nil, errors.New("invalid value for End")
+ }
+ rng.End = proto.String(strconv.FormatFloat(r.End, 'e', -1, 64))
+ }
+ return rng, nil
+}
+
+func protoToRange(rng *pb.FacetRefinement_Range) Range {
+ r := Range{Start: negInf, End: posInf}
+ if x, err := strconv.ParseFloat(rng.GetStart(), 64); err != nil {
+ r.Start = x
+ }
+ if x, err := strconv.ParseFloat(rng.GetEnd(), 64); err != nil {
+ r.End = x
+ }
+ return r
+}
+
// Iterator is the result of searching an index for a query or listing an
// index.
type Iterator struct {
@@ -598,20 +712,31 @@
listInclusive bool
searchRes []*pb.SearchResult
+ facetRes []*pb.FacetResult
searchQuery string
searchCursor *string
+ searchOffset int
sort *SortOptions
- fields []string
- exprs []FieldExpression
+ fields []string
+ exprs []FieldExpression
+ refinements []Facet
+ facetOpts []FacetSearchOption
more func(*Iterator) error
count int
- limit int // items left to return; -1 for unlimited.
+ limit int // items left to return; 0 for unlimited.
idsOnly bool
}
+// errIter returns an iterator that only returns the given error.
+func errIter(err string) *Iterator {
+ return &Iterator{
+ err: errors.New(err),
+ }
+}
+
// Done is returned when a query iteration has completed.
var Done = errors.New("search: query has no more results")
@@ -619,6 +744,13 @@
// query. It is only valid to call for iterators returned by Search.
func (t *Iterator) Count() int { return t.count }
+// fetchMore retrieves more results, if there are no errors or pending results.
+func (t *Iterator) fetchMore() {
+ if t.err == nil && len(t.listRes)+len(t.searchRes) == 0 && t.more != nil {
+ t.err = t.more(t)
+ }
+}
+
// Next returns the ID of the next result. When there are no more results,
// Done is returned as the error.
//
@@ -627,9 +759,7 @@
// will be filled with the indexed fields. dst is ignored if this iterator was
// created with an IDsOnly option.
func (t *Iterator) Next(dst interface{}) (string, error) {
- if t.err == nil && len(t.listRes)+len(t.searchRes) == 0 && t.more != nil {
- t.err = t.more(t)
- }
+ t.fetchMore()
if t.err != nil {
return "", t.err
}
@@ -643,6 +773,7 @@
case len(t.searchRes) != 0:
doc = t.searchRes[0].Document
exprs = t.searchRes[0].Expression
+ t.searchCursor = t.searchRes[0].Cursor
t.searchRes = t.searchRes[1:]
default:
return "", Done
@@ -651,24 +782,57 @@
return "", errors.New("search: internal error: no document returned")
}
if !t.idsOnly && dst != nil {
- metadata := &DocumentMetadata{
- Rank: int(doc.GetOrderId()),
- }
- if err := loadDoc(dst, doc.Field, exprs, metadata); err != nil {
+ if err := loadDoc(dst, doc, exprs); err != nil {
return "", err
}
}
- if t.limit > 0 {
- t.limit--
- if t.limit == 0 {
- t.more = nil // prevent further fetches
- }
- }
return doc.GetId(), nil
}
-// saveDoc converts from a struct pointer or FieldLoadSaver to protobufs.
-func saveDoc(src interface{}) ([]*pb.Field, *DocumentMetadata, error) {
+// Cursor returns the cursor associated with the current document (that is,
+// the document most recently returned by a call to Next).
+//
+// Passing this cursor in a future call to Search will cause those results
+// to commence with the first document after the current document.
+func (t *Iterator) Cursor() Cursor {
+ if t.searchCursor == nil {
+ return ""
+ }
+ return Cursor(*t.searchCursor)
+}
+
+// Facets returns the facets found within the search results, if any facets
+// were requested in the SearchOptions.
+func (t *Iterator) Facets() ([][]FacetResult, error) {
+ t.fetchMore()
+ if t.err != nil && t.err != Done {
+ return nil, t.err
+ }
+
+ var facets [][]FacetResult
+ for _, f := range t.facetRes {
+ fres := make([]FacetResult, 0, len(f.Value))
+ for _, v := range f.Value {
+ ref := v.Refinement
+ facet := FacetResult{
+ Facet: Facet{Name: ref.GetName()},
+ Count: int(v.GetCount()),
+ }
+ if ref.Value != nil {
+ facet.Value = Atom(*ref.Value)
+ } else {
+ facet.Value = protoToRange(ref.Range)
+ }
+ fres = append(fres, facet)
+ }
+ facets = append(facets, fres)
+ }
+ return facets, nil
+}
+
+// saveDoc converts from a struct pointer or
+// FieldLoadSaver/FieldMetadataLoadSaver to the Document protobuf.
+func saveDoc(src interface{}) (*pb.Document, error) {
var err error
var fields []Field
var meta *DocumentMetadata
@@ -679,10 +843,33 @@
fields, err = SaveStruct(src)
}
if err != nil {
- return nil, nil, err
+ return nil, err
}
- f, err := fieldsToProto(fields)
- return f, meta, err
+
+ fieldsProto, err := fieldsToProto(fields)
+ if err != nil {
+ return nil, err
+ }
+ d := &pb.Document{
+ Field: fieldsProto,
+ OrderId: proto.Int32(int32(time.Since(orderIDEpoch).Seconds())),
+ }
+ if meta != nil {
+ if meta.Rank != 0 {
+ if !validDocRank(meta.Rank) {
+ return nil, fmt.Errorf("search: invalid rank %d, must be [0, 2^31)", meta.Rank)
+ }
+ *d.OrderId = int32(meta.Rank)
+ }
+ if len(meta.Facets) > 0 {
+ facets, err := facetsToProto(meta.Facets)
+ if err != nil {
+ return nil, err
+ }
+ d.Facet = facets
+ }
+ }
+ return d, nil
}
func fieldsToProto(src []Field) ([]*pb.Field, error) {
@@ -757,12 +944,48 @@
return dst, nil
}
-// loadDoc converts from protobufs and document metadata to a struct pointer or
-// FieldLoadSaver/FieldMetadataLoadSaver. Two slices of fields may be provided:
-// src represents the document's stored fields; exprs is the derived expressions
-// requested by the developer. The latter may be empty.
-func loadDoc(dst interface{}, src, exprs []*pb.Field, meta *DocumentMetadata) (err error) {
- fields, err := protoToFields(src)
+func facetsToProto(src []Facet) ([]*pb.Facet, error) {
+ dst := make([]*pb.Facet, 0, len(src))
+ for _, f := range src {
+ if !validFieldName(f.Name) {
+ return nil, fmt.Errorf("search: invalid facet name %q", f.Name)
+ }
+ facetValue := &pb.FacetValue{}
+ switch x := f.Value.(type) {
+ case Atom:
+ if !utf8.ValidString(string(x)) {
+ return nil, fmt.Errorf("search: %q facet is invalid UTF-8: %q", f.Name, x)
+ }
+ facetValue.Type = pb.FacetValue_ATOM.Enum()
+ facetValue.StringValue = proto.String(string(x))
+ case float64:
+ if !validFloat(x) {
+ return nil, fmt.Errorf("search: numeric facet %q with invalid value %f", f.Name, x)
+ }
+ facetValue.Type = pb.FacetValue_NUMBER.Enum()
+ facetValue.StringValue = proto.String(strconv.FormatFloat(x, 'e', -1, 64))
+ default:
+ return nil, fmt.Errorf("search: unsupported facet type: %v", reflect.TypeOf(f.Value))
+ }
+ dst = append(dst, &pb.Facet{
+ Name: proto.String(f.Name),
+ Value: facetValue,
+ })
+ }
+ return dst, nil
+}
+
+// loadDoc converts from protobufs to a struct pointer or
+// FieldLoadSaver/FieldMetadataLoadSaver. The src param provides the document's
+// stored fields and facets, and any document metadata. An additional slice of
+// fields, exprs, may optionally be provided to contain any derived expressions
+// requested by the developer.
+func loadDoc(dst interface{}, src *pb.Document, exprs []*pb.Field) (err error) {
+ fields, err := protoToFields(src.Field)
+ if err != nil {
+ return err
+ }
+ facets, err := protoToFacets(src.Facet)
if err != nil {
return err
}
@@ -777,11 +1000,15 @@
}
fields = append(fields, exprFields...)
}
+ meta := &DocumentMetadata{
+ Rank: int(src.GetOrderId()),
+ Facets: facets,
+ }
switch x := dst.(type) {
case FieldLoadSaver:
return x.Load(fields, meta)
default:
- return LoadStruct(dst, fields)
+ return loadStructWithMeta(dst, fields, meta)
}
}
@@ -830,6 +1057,34 @@
return dst, nil
}
+func protoToFacets(facets []*pb.Facet) ([]Facet, error) {
+ if len(facets) == 0 {
+ return nil, nil
+ }
+ dst := make([]Facet, 0, len(facets))
+ for _, facet := range facets {
+ facetValue := facet.GetValue()
+ f := Facet{
+ Name: facet.GetName(),
+ }
+ switch facetValue.GetType() {
+ case pb.FacetValue_ATOM:
+ f.Value = Atom(facetValue.GetStringValue())
+ case pb.FacetValue_NUMBER:
+ sv := facetValue.GetStringValue()
+ x, err := strconv.ParseFloat(sv, 64)
+ if err != nil {
+ return nil, err
+ }
+ f.Value = x
+ default:
+ return nil, fmt.Errorf("search: internal error: unknown data type %s", facetValue.GetType())
+ }
+ dst = append(dst, f)
+ }
+ return dst, nil
+}
+
func namespaceMod(m proto.Message, namespace string) {
set := func(s **string) {
if *s == nil {
diff --git a/go/src/google.golang.org/appengine/search/search_test.go b/go/src/google.golang.org/appengine/search/search_test.go
index 6178c51..fd00654 100644
--- a/go/src/google.golang.org/appengine/search/search_test.go
+++ b/go/src/google.golang.org/appengine/search/search_test.go
@@ -140,7 +140,7 @@
func TestLoadDoc(t *testing.T) {
got, want := TestDoc{}, searchDoc
- if err := loadDoc(&got, protoFields, nil, nil); err != nil {
+ if err := loadDoc(&got, &pb.Document{Field: protoFields}, nil); err != nil {
t.Fatalf("loadDoc: %v", err)
}
if got != want {
@@ -149,12 +149,12 @@
}
func TestSaveDoc(t *testing.T) {
- got, _, err := saveDoc(&searchDoc)
+ got, err := saveDoc(&searchDoc)
if err != nil {
t.Fatalf("saveDoc: %v", err)
}
want := protoFields
- if !reflect.DeepEqual(got, want) {
+ if !reflect.DeepEqual(got.Field, want) {
t.Errorf("\ngot %v\nwant %v", got, want)
}
}
@@ -162,7 +162,7 @@
func TestLoadFieldList(t *testing.T) {
var got FieldList
want := searchFieldsWithLang
- if err := loadDoc(&got, protoFields, nil, nil); err != nil {
+ if err := loadDoc(&got, &pb.Document{Field: protoFields}, nil); err != nil {
t.Fatalf("loadDoc: %v", err)
}
if !reflect.DeepEqual(got, want) {
@@ -176,11 +176,11 @@
{Name: "Bar", Value: "私は日本人だ", Language: "jp"},
}
var got FieldList
- protoFields, _, err := saveDoc(fl)
+ doc, err := saveDoc(fl)
if err != nil {
t.Fatalf("saveDoc: %v", err)
}
- if err := loadDoc(&got, protoFields, nil, nil); err != nil {
+ if err := loadDoc(&got, doc, nil); err != nil {
t.Fatalf("loadDoc: %v", err)
}
if want := fl; !reflect.DeepEqual(&got, want) {
@@ -189,12 +189,12 @@
}
func TestSaveFieldList(t *testing.T) {
- got, _, err := saveDoc(&searchFields)
+ got, err := saveDoc(&searchFields)
if err != nil {
t.Fatalf("saveDoc: %v", err)
}
want := protoFields
- if !reflect.DeepEqual(got, want) {
+ if !reflect.DeepEqual(got.Field, want) {
t.Errorf("\ngot %v\nwant %v", got, want)
}
}
@@ -205,7 +205,8 @@
f.Derived = (i >= 2) // First 2 elements are "fields", next are "expressions".
want = append(want, f)
}
- if err := loadDoc(&got, protoFields[:2], protoFields[2:], nil); err != nil {
+ doc, expr := &pb.Document{Field: protoFields[:2]}, protoFields[2:]
+ if err := loadDoc(&got, doc, expr); err != nil {
t.Fatalf("loadDoc: %v", err)
}
if !reflect.DeepEqual(got, want) {
@@ -219,27 +220,32 @@
Meta: searchMeta,
Fields: searchFieldsWithLang,
}
- if err := loadDoc(&got, protoFields, nil, searchMeta); err != nil {
+ doc := &pb.Document{
+ Field: protoFields,
+ OrderId: proto.Int32(42),
+ }
+ if err := loadDoc(&got, doc, nil); err != nil {
t.Fatalf("loadDoc: %v", err)
}
if !reflect.DeepEqual(got, want) {
- t.Errorf("got %v\nwant %v", got, want)
+ t.Errorf("\ngot %v\nwant %v", got, want)
}
}
func TestSaveMeta(t *testing.T) {
- got, gotMeta, err := saveDoc(&FieldListWithMeta{
+ got, err := saveDoc(&FieldListWithMeta{
Meta: searchMeta,
Fields: searchFields,
})
if err != nil {
t.Fatalf("saveDoc: %v", err)
}
- if want := protoFields; !reflect.DeepEqual(got, want) {
- t.Errorf("\ngot %v\nwant %v", got, want)
+ want := &pb.Document{
+ Field: protoFields,
+ OrderId: proto.Int32(42),
}
- if want := searchMeta; !reflect.DeepEqual(gotMeta, want) {
- t.Errorf("\ngot %v\nwant %v", gotMeta, want)
+ if !proto.Equal(got, want) {
+ t.Errorf("\ngot %v\nwant %v", got, want)
}
}
@@ -251,7 +257,7 @@
{"Normal", true},
{"Also_OK_123", true},
{"Not so great", false},
- {"lower_case", false},
+ {"lower_case", true},
{"Exclaim!", false},
{"Hello세상아 안녕", false},
{"", false},
@@ -261,7 +267,7 @@
}
for _, tc := range testCases {
- _, _, err := saveDoc(&FieldList{
+ _, err := saveDoc(&FieldList{
Field{Name: tc.name, Value: "val"},
})
if err != nil && !strings.Contains(err.Error(), "invalid field name") {
@@ -288,7 +294,7 @@
}
for _, tt := range testCases {
- _, _, err := saveDoc(&FieldList{tt.field})
+ _, err := saveDoc(&FieldList{tt.field})
if err == nil != tt.valid {
t.Errorf("Field %v, got error %v, wanted valid %t", tt.field, err, tt.valid)
}
@@ -325,7 +331,7 @@
},
}
for _, tc := range testCases {
- _, _, err := saveDoc(&tc.fields)
+ _, err := saveDoc(&tc.fields)
if (err == nil) != (tc.errMsg == "") || (err != nil && !strings.Contains(err.Error(), tc.errMsg)) {
t.Errorf("%s: got err %v, wanted %q", tc.desc, err, tc.errMsg)
}
@@ -368,7 +374,7 @@
},
}
for _, tc := range testCases {
- err := loadDoc(tc.dst, tc.src, nil, nil)
+ err := loadDoc(tc.dst, &pb.Document{Field: tc.src}, nil)
if !reflect.DeepEqual(err, tc.err) {
t.Errorf("%s, got err %v, wanted %v", tc.desc, err, tc.err)
}
@@ -376,40 +382,49 @@
}
func TestLimit(t *testing.T) {
- more := func(it *Iterator) error {
- if it.limit == 0 {
- return errors.New("Iterator.limit should not be zero in next")
+ index, err := Open("Doc")
+ if err != nil {
+ t.Fatalf("err from Open: %v", err)
+ }
+ c := aetesting.FakeSingleContext(t, "search", "Search", func(req *pb.SearchRequest, res *pb.SearchResponse) error {
+ limit := 20 // Default per page.
+ if req.Params.Limit != nil {
+ limit = int(*req.Params.Limit)
}
- // Page up to 20 items at once.
- ret := 20
- if it.limit > 0 && it.limit < ret {
- ret = it.limit
- }
- it.listRes = make([]*pb.Document, ret)
- for i := range it.listRes {
- it.listRes[i] = &pb.Document{}
+ res.Status = &pb.RequestStatus{Code: pb.SearchServiceError_OK.Enum()}
+ res.MatchedCount = proto.Int64(int64(limit))
+ for i := 0; i < limit; i++ {
+ res.Result = append(res.Result, &pb.SearchResult{Document: &pb.Document{}})
+ res.Cursor = proto.String("moreresults")
}
return nil
+ })
+
+ const maxDocs = 500 // Limit maximum number of docs.
+ testCases := []struct {
+ limit, want int
+ }{
+ {limit: 0, want: maxDocs},
+ {limit: 42, want: 42},
+ {limit: 100, want: 100},
+ {limit: 1000, want: maxDocs},
}
- it := &Iterator{
- more: more,
- limit: 42,
- }
-
- count := 0
- for {
- _, err := it.Next(nil)
- if err == Done {
- break
+ for _, tt := range testCases {
+ it := index.Search(c, "gopher", &SearchOptions{Limit: tt.limit, IDsOnly: true})
+ count := 0
+ for ; count < maxDocs; count++ {
+ _, err := it.Next(nil)
+ if err == Done {
+ break
+ }
+ if err != nil {
+ t.Fatalf("err after %d: %v", count, err)
+ }
}
- if err != nil {
- t.Fatalf("err after %d: %v", count, err)
+ if count != tt.want {
+ t.Errorf("got %d results, expected %d", count, tt.want)
}
- count++
- }
- if count != 42 {
- t.Errorf("got %d results, expected 42", count)
}
}
@@ -516,7 +531,7 @@
t.Fatalf("err from Open: %v", err)
}
- noErr := errors.New("") // sentinel error when there isn't one…
+ noErr := errors.New("") // Sentinel err to return to prevent sending request.
testCases := []struct {
desc string
@@ -648,3 +663,244 @@
}
}
}
+
+func TestBasicSearchOpts(t *testing.T) {
+ index, err := Open("Doc")
+ if err != nil {
+ t.Fatalf("err from Open: %v", err)
+ }
+
+ noErr := errors.New("") // Sentinel err to return to prevent sending request.
+
+ testCases := []struct {
+ desc string
+ facetOpts []FacetSearchOption
+ cursor Cursor
+ offset int
+ want *pb.SearchParams
+ wantErr string
+ }{
+ {
+ desc: "No options",
+ want: &pb.SearchParams{},
+ },
+ {
+ desc: "Default auto discovery",
+ facetOpts: []FacetSearchOption{
+ AutoFacetDiscovery(0, 0),
+ },
+ want: &pb.SearchParams{
+ AutoDiscoverFacetCount: proto.Int32(10),
+ },
+ },
+ {
+ desc: "Auto discovery",
+ facetOpts: []FacetSearchOption{
+ AutoFacetDiscovery(7, 12),
+ },
+ want: &pb.SearchParams{
+ AutoDiscoverFacetCount: proto.Int32(7),
+ FacetAutoDetectParam: &pb.FacetAutoDetectParam{
+ ValueLimit: proto.Int32(12),
+ },
+ },
+ },
+ {
+ desc: "Param Depth",
+ facetOpts: []FacetSearchOption{
+ AutoFacetDiscovery(7, 12),
+ },
+ want: &pb.SearchParams{
+ AutoDiscoverFacetCount: proto.Int32(7),
+ FacetAutoDetectParam: &pb.FacetAutoDetectParam{
+ ValueLimit: proto.Int32(12),
+ },
+ },
+ },
+ {
+ desc: "Doc depth",
+ facetOpts: []FacetSearchOption{
+ FacetDocumentDepth(123),
+ },
+ want: &pb.SearchParams{
+ FacetDepth: proto.Int32(123),
+ },
+ },
+ {
+ desc: "Facet discovery",
+ facetOpts: []FacetSearchOption{
+ FacetDiscovery("colour"),
+ FacetDiscovery("size", Atom("M"), Atom("L")),
+ FacetDiscovery("price", LessThan(7), Range{7, 14}, AtLeast(14)),
+ },
+ want: &pb.SearchParams{
+ IncludeFacet: []*pb.FacetRequest{
+ {Name: proto.String("colour")},
+ {Name: proto.String("size"), Params: &pb.FacetRequestParam{
+ ValueConstraint: []string{"M", "L"},
+ }},
+ {Name: proto.String("price"), Params: &pb.FacetRequestParam{
+ Range: []*pb.FacetRange{
+ {End: proto.String("7e+00")},
+ {Start: proto.String("7e+00"), End: proto.String("1.4e+01")},
+ {Start: proto.String("1.4e+01")},
+ },
+ }},
+ },
+ },
+ },
+ {
+ desc: "Facet discovery - bad value",
+ facetOpts: []FacetSearchOption{
+ FacetDiscovery("colour", true),
+ },
+ wantErr: "bad FacetSearchOption: unsupported value type bool",
+ },
+ {
+ desc: "Facet discovery - mix value types",
+ facetOpts: []FacetSearchOption{
+ FacetDiscovery("colour", Atom("blue"), AtLeast(7)),
+ },
+ wantErr: "bad FacetSearchOption: values must all be Atom, or must all be Range",
+ },
+ {
+ desc: "Facet discovery - invalid range",
+ facetOpts: []FacetSearchOption{
+ FacetDiscovery("colour", Range{negInf, posInf}),
+ },
+ wantErr: "bad FacetSearchOption: invalid range: either Start or End must be finite",
+ },
+ {
+ desc: "Cursor",
+ cursor: Cursor("mycursor"),
+ want: &pb.SearchParams{
+ Cursor: proto.String("mycursor"),
+ },
+ },
+ {
+ desc: "Offset",
+ offset: 121,
+ want: &pb.SearchParams{
+ Offset: proto.Int32(121),
+ },
+ },
+ {
+ desc: "Cursor and Offset set",
+ cursor: Cursor("mycursor"),
+ offset: 121,
+ wantErr: "at most one of Cursor and Offset may be specified",
+ },
+ }
+
+ for _, tt := range testCases {
+ c := aetesting.FakeSingleContext(t, "search", "Search", func(req *pb.SearchRequest, _ *pb.SearchResponse) error {
+ if tt.want == nil {
+ t.Errorf("%s: expected call to fail", tt.desc)
+ return nil
+ }
+ // Set default fields.
+ tt.want.Query = proto.String("gopher")
+ tt.want.IndexSpec = &pb.IndexSpec{Name: proto.String("Doc")}
+ tt.want.CursorType = pb.SearchParams_PER_RESULT.Enum()
+ tt.want.FieldSpec = &pb.FieldSpec{}
+ if got := req.Params; !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("%s: params=%v; want %v", tt.desc, got, tt.want)
+ }
+ return noErr // Always return some error to prevent response parsing.
+ })
+
+ it := index.Search(c, "gopher", &SearchOptions{
+ Facets: tt.facetOpts,
+ Cursor: tt.cursor,
+ Offset: tt.offset,
+ })
+ _, err := it.Next(nil)
+ if err == nil {
+ t.Fatalf("%s: err==nil; should not happen", tt.desc)
+ }
+ if err.Error() != tt.wantErr {
+ t.Errorf("%s: got error %q, want %q", tt.desc, err, tt.wantErr)
+ }
+ }
+}
+
+func TestFacetRefinements(t *testing.T) {
+ index, err := Open("Doc")
+ if err != nil {
+ t.Fatalf("err from Open: %v", err)
+ }
+
+ noErr := errors.New("") // Sentinel err to return to prevent sending request.
+
+ testCases := []struct {
+ desc string
+ refine []Facet
+ want []*pb.FacetRefinement
+ wantErr string
+ }{
+ {
+ desc: "No refinements",
+ },
+ {
+ desc: "Basic",
+ refine: []Facet{
+ {Name: "fur", Value: Atom("fluffy")},
+ {Name: "age", Value: LessThan(123)},
+ {Name: "age", Value: AtLeast(0)},
+ {Name: "legs", Value: Range{Start: 3, End: 5}},
+ },
+ want: []*pb.FacetRefinement{
+ {Name: proto.String("fur"), Value: proto.String("fluffy")},
+ {Name: proto.String("age"), Range: &pb.FacetRefinement_Range{End: proto.String("1.23e+02")}},
+ {Name: proto.String("age"), Range: &pb.FacetRefinement_Range{Start: proto.String("0e+00")}},
+ {Name: proto.String("legs"), Range: &pb.FacetRefinement_Range{Start: proto.String("3e+00"), End: proto.String("5e+00")}},
+ },
+ },
+ {
+ desc: "Infinite range",
+ refine: []Facet{
+ {Name: "age", Value: Range{Start: negInf, End: posInf}},
+ },
+ wantErr: `search: refinement for facet "age": either Start or End must be finite`,
+ },
+ {
+ desc: "Bad End value in range",
+ refine: []Facet{
+ {Name: "age", Value: LessThan(2147483648)},
+ },
+ wantErr: `search: refinement for facet "age": invalid value for End`,
+ },
+ {
+ desc: "Bad Start value in range",
+ refine: []Facet{
+ {Name: "age", Value: AtLeast(-2147483649)},
+ },
+ wantErr: `search: refinement for facet "age": invalid value for Start`,
+ },
+ {
+ desc: "Unknown value type",
+ refine: []Facet{
+ {Name: "age", Value: "you can't use strings!"},
+ },
+ wantErr: `search: unsupported refinement for facet "age" of type string`,
+ },
+ }
+
+ for _, tt := range testCases {
+ c := aetesting.FakeSingleContext(t, "search", "Search", func(req *pb.SearchRequest, _ *pb.SearchResponse) error {
+ if got := req.Params.FacetRefinement; !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("%s: params.FacetRefinement=%v; want %v", tt.desc, got, tt.want)
+ }
+ return noErr // Always return some error to prevent response parsing.
+ })
+
+ it := index.Search(c, "gopher", &SearchOptions{Refinements: tt.refine})
+ _, err := it.Next(nil)
+ if err == nil {
+ t.Fatalf("%s: err==nil; should not happen", tt.desc)
+ }
+ if err.Error() != tt.wantErr {
+ t.Errorf("%s: got error %q, want %q", tt.desc, err, tt.wantErr)
+ }
+ }
+}
diff --git a/go/src/google.golang.org/appengine/search/struct.go b/go/src/google.golang.org/appengine/search/struct.go
new file mode 100644
index 0000000..083c97f
--- /dev/null
+++ b/go/src/google.golang.org/appengine/search/struct.go
@@ -0,0 +1,245 @@
+// Copyright 2015 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package search
+
+import (
+ "fmt"
+ "reflect"
+ "strings"
+ "sync"
+)
+
+// ErrFieldMismatch is returned when a field is to be loaded into a different
+// than the one it was stored from, or when a field is missing or unexported in
+// the destination struct.
+type ErrFieldMismatch struct {
+ FieldName string
+ Reason string
+}
+
+func (e *ErrFieldMismatch) Error() string {
+ return fmt.Sprintf("search: cannot load field %q: %s", e.FieldName, e.Reason)
+}
+
+// ErrFacetMismatch is returned when a facet is to be loaded into a different
+// type than the one it was stored from, or when a field is missing or
+// unexported in the destination struct. StructType is the type of the struct
+// pointed to by the destination argument passed to Iterator.Next.
+type ErrFacetMismatch struct {
+ StructType reflect.Type
+ FacetName string
+ Reason string
+}
+
+func (e *ErrFacetMismatch) Error() string {
+ return fmt.Sprintf("search: cannot load facet %q into a %q: %s", e.FacetName, e.StructType, e.Reason)
+}
+
+// structCodec defines how to convert a given struct to/from a search document.
+type structCodec struct {
+ // byIndex returns the struct tag for the i'th struct field.
+ byIndex []structTag
+
+ // fieldByName returns the index of the struct field for the given field name.
+ fieldByName map[string]int
+
+ // facetByName returns the index of the struct field for the given facet name,
+ facetByName map[string]int
+}
+
+// structTag holds a structured version of each struct field's parsed tag.
+type structTag struct {
+ name string
+ facet bool
+}
+
+var (
+ codecsMu sync.RWMutex
+ codecs = map[reflect.Type]*structCodec{}
+)
+
+func loadCodec(t reflect.Type) (*structCodec, error) {
+ codecsMu.RLock()
+ codec, ok := codecs[t]
+ codecsMu.RUnlock()
+ if ok {
+ return codec, nil
+ }
+
+ codecsMu.Lock()
+ defer codecsMu.Unlock()
+ if codec, ok := codecs[t]; ok {
+ return codec, nil
+ }
+
+ codec = &structCodec{
+ fieldByName: make(map[string]int),
+ facetByName: make(map[string]int),
+ }
+
+ for i, I := 0, t.NumField(); i < I; i++ {
+ f := t.Field(i)
+ name, opts := f.Tag.Get("search"), ""
+ if i := strings.Index(name, ","); i != -1 {
+ name, opts = name[:i], name[i+1:]
+ }
+ // TODO(davidday): Support name=="-" as per datastore.
+ if name == "" {
+ name = f.Name
+ } else if !validFieldName(name) {
+ return nil, fmt.Errorf("search: struct tag has invalid field name: %q", name)
+ }
+ facet := opts == "facet"
+ codec.byIndex = append(codec.byIndex, structTag{name: name, facet: facet})
+ if facet {
+ codec.facetByName[name] = i
+ } else {
+ codec.fieldByName[name] = i
+ }
+ }
+
+ codecs[t] = codec
+ return codec, nil
+}
+
+// structFLS adapts a struct to be a FieldLoadSaver.
+type structFLS struct {
+ v reflect.Value
+ codec *structCodec
+}
+
+func (s structFLS) Load(fields []Field, meta *DocumentMetadata) error {
+ var err error
+ for _, field := range fields {
+ i, ok := s.codec.fieldByName[field.Name]
+ if !ok {
+ // Note the error, but keep going.
+ err = &ErrFieldMismatch{
+ FieldName: field.Name,
+ Reason: "no such struct field",
+ }
+ continue
+
+ }
+ f := s.v.Field(i)
+ if !f.CanSet() {
+ // Note the error, but keep going.
+ err = &ErrFieldMismatch{
+ FieldName: field.Name,
+ Reason: "cannot set struct field",
+ }
+ continue
+ }
+ v := reflect.ValueOf(field.Value)
+ if ft, vt := f.Type(), v.Type(); ft != vt {
+ err = &ErrFieldMismatch{
+ FieldName: field.Name,
+ Reason: fmt.Sprintf("type mismatch: %v for %v data", ft, vt),
+ }
+ continue
+ }
+ f.Set(v)
+ }
+ if meta == nil {
+ return nil
+ }
+ for _, facet := range meta.Facets {
+ i, ok := s.codec.facetByName[facet.Name]
+ if !ok {
+ // Note the error, but keep going.
+ if err == nil {
+ err = &ErrFacetMismatch{
+ StructType: s.v.Type(),
+ FacetName: facet.Name,
+ Reason: "no matching field found",
+ }
+ }
+ continue
+ }
+ f := s.v.Field(i)
+ if !f.CanSet() {
+ // Note the error, but keep going.
+ if err == nil {
+ err = &ErrFacetMismatch{
+ StructType: s.v.Type(),
+ FacetName: facet.Name,
+ Reason: "unable to set unexported field of struct",
+ }
+ }
+ continue
+ }
+ v := reflect.ValueOf(facet.Value)
+ if ft, vt := f.Type(), v.Type(); ft != vt {
+ if err == nil {
+ err = &ErrFacetMismatch{
+ StructType: s.v.Type(),
+ FacetName: facet.Name,
+ Reason: fmt.Sprintf("type mismatch: %v for %d data", ft, vt),
+ }
+ continue
+ }
+ }
+ f.Set(v)
+ }
+ return err
+}
+
+func (s structFLS) Save() ([]Field, *DocumentMetadata, error) {
+ fields := make([]Field, 0, len(s.codec.fieldByName))
+ var facets []Facet
+ for i, tag := range s.codec.byIndex {
+ f := s.v.Field(i)
+ if !f.CanSet() {
+ continue
+ }
+ if tag.facet {
+ facets = append(facets, Facet{Name: tag.name, Value: f.Interface()})
+ } else {
+ fields = append(fields, Field{Name: tag.name, Value: f.Interface()})
+ }
+ }
+ return fields, &DocumentMetadata{Facets: facets}, nil
+}
+
+// newStructFLS returns a FieldLoadSaver for the struct pointer p.
+func newStructFLS(p interface{}) (FieldLoadSaver, error) {
+ v := reflect.ValueOf(p)
+ if v.Kind() != reflect.Ptr || v.IsNil() || v.Elem().Kind() != reflect.Struct {
+ return nil, ErrInvalidDocumentType
+ }
+ codec, err := loadCodec(v.Elem().Type())
+ if err != nil {
+ return nil, err
+ }
+ return structFLS{v.Elem(), codec}, nil
+}
+
+func loadStructWithMeta(dst interface{}, f []Field, meta *DocumentMetadata) error {
+ x, err := newStructFLS(dst)
+ if err != nil {
+ return err
+ }
+ return x.Load(f, meta)
+}
+
+func saveStructWithMeta(src interface{}) ([]Field, *DocumentMetadata, error) {
+ x, err := newStructFLS(src)
+ if err != nil {
+ return nil, nil, err
+ }
+ return x.Save()
+}
+
+// LoadStruct loads the fields from f to dst. dst must be a struct pointer.
+func LoadStruct(dst interface{}, f []Field) error {
+ return loadStructWithMeta(dst, f, nil)
+}
+
+// SaveStruct returns the fields from src as a slice of Field.
+// src must be a struct pointer.
+func SaveStruct(src interface{}) ([]Field, error) {
+ f, _, err := saveStructWithMeta(src)
+ return f, err
+}
diff --git a/go/src/google.golang.org/appengine/search/struct_test.go b/go/src/google.golang.org/appengine/search/struct_test.go
new file mode 100644
index 0000000..45b8ab2
--- /dev/null
+++ b/go/src/google.golang.org/appengine/search/struct_test.go
@@ -0,0 +1,172 @@
+// Copyright 2015 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package search
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestLoadingStruct(t *testing.T) {
+ testCases := []struct {
+ desc string
+ fields []Field
+ meta *DocumentMetadata
+ want interface{}
+ wantErr bool
+ }{
+ {
+ desc: "Basic struct",
+ fields: []Field{
+ {Name: "Name", Value: "Gopher"},
+ {Name: "Legs", Value: float64(4)},
+ },
+ want: &struct {
+ Name string
+ Legs float64
+ }{"Gopher", 4},
+ },
+ {
+ desc: "Struct with tags",
+ fields: []Field{
+ {Name: "Name", Value: "Gopher"},
+ {Name: "about", Value: "Likes slide rules."},
+ },
+ meta: &DocumentMetadata{Facets: []Facet{
+ {Name: "Legs", Value: float64(4)},
+ {Name: "Fur", Value: Atom("furry")},
+ }},
+ want: &struct {
+ Name string
+ Info string `search:"about"`
+ Legs float64 `search:",facet"`
+ Fuzz Atom `search:"Fur,facet"`
+ }{"Gopher", "Likes slide rules.", 4, Atom("furry")},
+ },
+ {
+ desc: "Bad field from tag",
+ want: &struct {
+ AlphaBeta string `search:"αβ"`
+ }{},
+ wantErr: true,
+ },
+ {
+ desc: "Ignore missing field",
+ fields: []Field{
+ {Name: "Meaning", Value: float64(42)},
+ },
+ want: &struct{}{},
+ },
+ {
+ desc: "Ignore unsettable field",
+ fields: []Field{
+ {Name: "meaning", Value: float64(42)},
+ },
+ want: &struct{ meaning float64 }{}, // field not populated.
+ },
+ {
+ desc: "Error on missing facet",
+ meta: &DocumentMetadata{Facets: []Facet{
+ {Name: "Set", Value: Atom("yes")},
+ {Name: "Missing", Value: Atom("no")},
+ }},
+ want: &struct {
+ Set Atom `search:",facet"`
+ }{Atom("yes")},
+ wantErr: true,
+ },
+ {
+ desc: "Error on unsettable facet",
+ meta: &DocumentMetadata{Facets: []Facet{
+ {Name: "Set", Value: Atom("yes")},
+ {Name: "unset", Value: Atom("no")},
+ }},
+ want: &struct {
+ Set Atom `search:",facet"`
+ }{Atom("yes")},
+ wantErr: true,
+ },
+ }
+
+ for _, tt := range testCases {
+ // Make a pointer to an empty version of what want points to.
+ dst := reflect.New(reflect.TypeOf(tt.want).Elem()).Interface()
+ err := loadStructWithMeta(dst, tt.fields, tt.meta)
+ if err != nil != tt.wantErr {
+ t.Errorf("%s: got err %v; want err %t", tt.desc, err, tt.wantErr)
+ continue
+ }
+ if !reflect.DeepEqual(dst, tt.want) {
+ t.Errorf("%s: doesn't match\ngot: %v\nwant: %v", tt.desc, dst, tt.want)
+ }
+ }
+}
+
+func TestSavingStruct(t *testing.T) {
+ testCases := []struct {
+ desc string
+ doc interface{}
+ wantFields []Field
+ wantFacets []Facet
+ }{
+ {
+ desc: "Basic struct",
+ doc: &struct {
+ Name string
+ Legs float64
+ }{"Gopher", 4},
+ wantFields: []Field{
+ {Name: "Name", Value: "Gopher"},
+ {Name: "Legs", Value: float64(4)},
+ },
+ },
+ {
+ desc: "Struct with tags",
+ doc: &struct {
+ Name string
+ Info string `search:"about"`
+ Legs float64 `search:",facet"`
+ Fuzz Atom `search:"Fur,facet"`
+ }{"Gopher", "Likes slide rules.", 4, Atom("furry")},
+ wantFields: []Field{
+ {Name: "Name", Value: "Gopher"},
+ {Name: "about", Value: "Likes slide rules."},
+ },
+ wantFacets: []Facet{
+ {Name: "Legs", Value: float64(4)},
+ {Name: "Fur", Value: Atom("furry")},
+ },
+ },
+ {
+ desc: "Ignore unexported struct fields",
+ doc: &struct {
+ Name string
+ info string
+ Legs float64 `search:",facet"`
+ fuzz Atom `search:",facet"`
+ }{"Gopher", "Likes slide rules.", 4, Atom("furry")},
+ wantFields: []Field{
+ {Name: "Name", Value: "Gopher"},
+ },
+ wantFacets: []Facet{
+ {Name: "Legs", Value: float64(4)},
+ },
+ },
+ }
+
+ for _, tt := range testCases {
+ fields, meta, err := saveStructWithMeta(tt.doc)
+ if err != nil {
+ t.Errorf("%s: got err %v; want nil", tt.desc, err)
+ continue
+ }
+ if !reflect.DeepEqual(fields, tt.wantFields) {
+ t.Errorf("%s: fields don't match\ngot: %v\nwant: %v", tt.desc, fields, tt.wantFields)
+ }
+ if facets := meta.Facets; !reflect.DeepEqual(facets, tt.wantFacets) {
+ t.Errorf("%s: facets don't match\ngot: %v\nwant: %v", tt.desc, facets, tt.wantFacets)
+ }
+ }
+}
diff --git a/go/src/google.golang.org/appengine/socket/doc.go b/go/src/google.golang.org/appengine/socket/doc.go
new file mode 100644
index 0000000..1e23553
--- /dev/null
+++ b/go/src/google.golang.org/appengine/socket/doc.go
@@ -0,0 +1,10 @@
+// Copyright 2012 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+// Package socket provides outbound network sockets.
+//
+// This package is only required in the classic App Engine environment.
+// Applications running only in the Managed VM hosting environment should
+// use the standard library's net package.
+package socket
diff --git a/go/src/google.golang.org/appengine/socket/socket_classic.go b/go/src/google.golang.org/appengine/socket/socket_classic.go
new file mode 100644
index 0000000..0ad50e2
--- /dev/null
+++ b/go/src/google.golang.org/appengine/socket/socket_classic.go
@@ -0,0 +1,290 @@
+// Copyright 2012 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+// +build appengine
+
+package socket
+
+import (
+ "fmt"
+ "io"
+ "net"
+ "strconv"
+ "time"
+
+ "github.com/golang/protobuf/proto"
+ "golang.org/x/net/context"
+ "google.golang.org/appengine/internal"
+
+ pb "google.golang.org/appengine/internal/socket"
+)
+
+// Dial connects to the address addr on the network protocol.
+// The address format is host:port, where host may be a hostname or an IP address.
+// Known protocols are "tcp" and "udp".
+// The returned connection satisfies net.Conn, and is valid while ctx is valid;
+// if the connection is to be used after ctx becomes invalid, invoke SetContext
+// with the new context.
+func Dial(ctx context.Context, protocol, addr string) (*Conn, error) {
+ return DialTimeout(ctx, protocol, addr, 0)
+}
+
+var ipFamilies = []pb.CreateSocketRequest_SocketFamily{
+ pb.CreateSocketRequest_IPv4,
+ pb.CreateSocketRequest_IPv6,
+}
+
+// DialTimeout is like Dial but takes a timeout.
+// The timeout includes name resolution, if required.
+func DialTimeout(ctx context.Context, protocol, addr string, timeout time.Duration) (*Conn, error) {
+ dialCtx := ctx // Used for dialing and name resolution, but not stored in the *Conn.
+ if timeout > 0 {
+ var cancel context.CancelFunc
+ dialCtx, cancel = context.WithTimeout(ctx, timeout)
+ defer cancel()
+ }
+
+ host, portStr, err := net.SplitHostPort(addr)
+ if err != nil {
+ return nil, err
+ }
+ port, err := strconv.Atoi(portStr)
+ if err != nil {
+ return nil, fmt.Errorf("socket: bad port %q: %v", portStr, err)
+ }
+
+ var prot pb.CreateSocketRequest_SocketProtocol
+ switch protocol {
+ case "tcp":
+ prot = pb.CreateSocketRequest_TCP
+ case "udp":
+ prot = pb.CreateSocketRequest_UDP
+ default:
+ return nil, fmt.Errorf("socket: unknown protocol %q", protocol)
+ }
+
+ packedAddrs, resolved, err := resolve(dialCtx, ipFamilies, host)
+ if err != nil {
+ return nil, fmt.Errorf("socket: failed resolving %q: %v", host, err)
+ }
+ if len(packedAddrs) == 0 {
+ return nil, fmt.Errorf("no addresses for %q", host)
+ }
+
+ packedAddr := packedAddrs[0] // use first address
+ fam := pb.CreateSocketRequest_IPv4
+ if len(packedAddr) == net.IPv6len {
+ fam = pb.CreateSocketRequest_IPv6
+ }
+
+ req := &pb.CreateSocketRequest{
+ Family: fam.Enum(),
+ Protocol: prot.Enum(),
+ RemoteIp: &pb.AddressPort{
+ Port: proto.Int32(int32(port)),
+ PackedAddress: packedAddr,
+ },
+ }
+ if resolved {
+ req.RemoteIp.HostnameHint = &host
+ }
+ res := &pb.CreateSocketReply{}
+ if err := internal.Call(dialCtx, "remote_socket", "CreateSocket", req, res); err != nil {
+ return nil, err
+ }
+
+ return &Conn{
+ ctx: ctx,
+ desc: res.GetSocketDescriptor(),
+ prot: prot,
+ local: res.ProxyExternalIp,
+ remote: req.RemoteIp,
+ }, nil
+}
+
+// LookupIP returns the given host's IP addresses.
+func LookupIP(ctx context.Context, host string) (addrs []net.IP, err error) {
+ packedAddrs, _, err := resolve(ctx, ipFamilies, host)
+ if err != nil {
+ return nil, fmt.Errorf("socket: failed resolving %q: %v", host, err)
+ }
+ addrs = make([]net.IP, len(packedAddrs))
+ for i, pa := range packedAddrs {
+ addrs[i] = net.IP(pa)
+ }
+ return addrs, nil
+}
+
+func resolve(ctx context.Context, fams []pb.CreateSocketRequest_SocketFamily, host string) ([][]byte, bool, error) {
+ // Check if it's an IP address.
+ if ip := net.ParseIP(host); ip != nil {
+ if ip := ip.To4(); ip != nil {
+ return [][]byte{ip}, false, nil
+ }
+ return [][]byte{ip}, false, nil
+ }
+
+ req := &pb.ResolveRequest{
+ Name: &host,
+ AddressFamilies: fams,
+ }
+ res := &pb.ResolveReply{}
+ if err := internal.Call(ctx, "remote_socket", "Resolve", req, res); err != nil {
+ // XXX: need to map to pb.ResolveReply_ErrorCode?
+ return nil, false, err
+ }
+ return res.PackedAddress, true, nil
+}
+
+// withDeadline is like context.WithDeadline, except it ignores the zero deadline.
+func withDeadline(parent context.Context, deadline time.Time) (context.Context, context.CancelFunc) {
+ if deadline.IsZero() {
+ return parent, func() {}
+ }
+ return context.WithDeadline(parent, deadline)
+}
+
+// Conn represents a socket connection.
+// It implements net.Conn.
+type Conn struct {
+ ctx context.Context
+ desc string
+ offset int64
+
+ prot pb.CreateSocketRequest_SocketProtocol
+ local, remote *pb.AddressPort
+
+ readDeadline, writeDeadline time.Time // optional
+}
+
+// SetContext sets the context that is used by this Conn.
+// It is usually used only when using a Conn that was created in a different context,
+// such as when a connection is created during a warmup request but used while
+// servicing a user request.
+func (cn *Conn) SetContext(ctx context.Context) {
+ cn.ctx = ctx
+}
+
+func (cn *Conn) Read(b []byte) (n int, err error) {
+ const maxRead = 1 << 20
+ if len(b) > maxRead {
+ b = b[:maxRead]
+ }
+
+ req := &pb.ReceiveRequest{
+ SocketDescriptor: &cn.desc,
+ DataSize: proto.Int32(int32(len(b))),
+ }
+ res := &pb.ReceiveReply{}
+ if !cn.readDeadline.IsZero() {
+ req.TimeoutSeconds = proto.Float64(cn.readDeadline.Sub(time.Now()).Seconds())
+ }
+ ctx, cancel := withDeadline(cn.ctx, cn.readDeadline)
+ defer cancel()
+ if err := internal.Call(ctx, "remote_socket", "Receive", req, res); err != nil {
+ return 0, err
+ }
+ if len(res.Data) == 0 {
+ return 0, io.EOF
+ }
+ if len(res.Data) > len(b) {
+ return 0, fmt.Errorf("socket: internal error: read too much data: %d > %d", len(res.Data), len(b))
+ }
+ return copy(b, res.Data), nil
+}
+
+func (cn *Conn) Write(b []byte) (n int, err error) {
+ const lim = 1 << 20 // max per chunk
+
+ for n < len(b) {
+ chunk := b[n:]
+ if len(chunk) > lim {
+ chunk = chunk[:lim]
+ }
+
+ req := &pb.SendRequest{
+ SocketDescriptor: &cn.desc,
+ Data: chunk,
+ StreamOffset: &cn.offset,
+ }
+ res := &pb.SendReply{}
+ if !cn.writeDeadline.IsZero() {
+ req.TimeoutSeconds = proto.Float64(cn.writeDeadline.Sub(time.Now()).Seconds())
+ }
+ ctx, cancel := withDeadline(cn.ctx, cn.writeDeadline)
+ defer cancel()
+ if err = internal.Call(ctx, "remote_socket", "Send", req, res); err != nil {
+ // assume zero bytes were sent in this RPC
+ break
+ }
+ n += int(res.GetDataSent())
+ cn.offset += int64(res.GetDataSent())
+ }
+
+ return
+}
+
+func (cn *Conn) Close() error {
+ req := &pb.CloseRequest{
+ SocketDescriptor: &cn.desc,
+ }
+ res := &pb.CloseReply{}
+ if err := internal.Call(cn.ctx, "remote_socket", "Close", req, res); err != nil {
+ return err
+ }
+ cn.desc = "CLOSED"
+ return nil
+}
+
+func addr(prot pb.CreateSocketRequest_SocketProtocol, ap *pb.AddressPort) net.Addr {
+ if ap == nil {
+ return nil
+ }
+ switch prot {
+ case pb.CreateSocketRequest_TCP:
+ return &net.TCPAddr{
+ IP: net.IP(ap.PackedAddress),
+ Port: int(*ap.Port),
+ }
+ case pb.CreateSocketRequest_UDP:
+ return &net.UDPAddr{
+ IP: net.IP(ap.PackedAddress),
+ Port: int(*ap.Port),
+ }
+ }
+ panic("unknown protocol " + prot.String())
+}
+
+func (cn *Conn) LocalAddr() net.Addr { return addr(cn.prot, cn.local) }
+func (cn *Conn) RemoteAddr() net.Addr { return addr(cn.prot, cn.remote) }
+
+func (cn *Conn) SetDeadline(t time.Time) error {
+ cn.readDeadline = t
+ cn.writeDeadline = t
+ return nil
+}
+
+func (cn *Conn) SetReadDeadline(t time.Time) error {
+ cn.readDeadline = t
+ return nil
+}
+
+func (cn *Conn) SetWriteDeadline(t time.Time) error {
+ cn.writeDeadline = t
+ return nil
+}
+
+// KeepAlive signals that the connection is still in use.
+// It may be called to prevent the socket being closed due to inactivity.
+func (cn *Conn) KeepAlive() error {
+ req := &pb.GetSocketNameRequest{
+ SocketDescriptor: &cn.desc,
+ }
+ res := &pb.GetSocketNameReply{}
+ return internal.Call(cn.ctx, "remote_socket", "GetSocketName", req, res)
+}
+
+func init() {
+ internal.RegisterErrorCodeMap("remote_socket", pb.RemoteSocketServiceError_ErrorCode_name)
+}
diff --git a/go/src/google.golang.org/appengine/socket/socket_vm.go b/go/src/google.golang.org/appengine/socket/socket_vm.go
new file mode 100644
index 0000000..ed98ac2
--- /dev/null
+++ b/go/src/google.golang.org/appengine/socket/socket_vm.go
@@ -0,0 +1,64 @@
+// Copyright 2015 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+// +build !appengine
+
+package socket
+
+import (
+ "net"
+ "time"
+
+ "golang.org/x/net/context"
+)
+
+// Dial connects to the address addr on the network protocol.
+// The address format is host:port, where host may be a hostname or an IP address.
+// Known protocols are "tcp" and "udp".
+// The returned connection satisfies net.Conn, and is valid while ctx is valid;
+// if the connection is to be used after ctx becomes invalid, invoke SetContext
+// with the new context.
+func Dial(ctx context.Context, protocol, addr string) (*Conn, error) {
+ conn, err := net.Dial(protocol, addr)
+ if err != nil {
+ return nil, err
+ }
+ return &Conn{conn}, nil
+}
+
+// DialTimeout is like Dial but takes a timeout.
+// The timeout includes name resolution, if required.
+func DialTimeout(ctx context.Context, protocol, addr string, timeout time.Duration) (*Conn, error) {
+ conn, err := net.DialTimeout(protocol, addr, timeout)
+ if err != nil {
+ return nil, err
+ }
+ return &Conn{conn}, nil
+}
+
+// LookupIP returns the given host's IP addresses.
+func LookupIP(ctx context.Context, host string) (addrs []net.IP, err error) {
+ return net.LookupIP(host)
+}
+
+// Conn represents a socket connection.
+// It implements net.Conn.
+type Conn struct {
+ net.Conn
+}
+
+// SetContext sets the context that is used by this Conn.
+// It is usually used only when using a Conn that was created in a different context,
+// such as when a connection is created during a warmup request but used while
+// servicing a user request.
+func (cn *Conn) SetContext(ctx context.Context) {
+ // This function is not required on managed VMs.
+}
+
+// KeepAlive signals that the connection is still in use.
+// It may be called to prevent the socket being closed due to inactivity.
+func (cn *Conn) KeepAlive() error {
+ // This function is not required on managed VMs.
+ return nil
+}
diff --git a/go/src/google.golang.org/appengine/taskqueue/ns_classic.go b/go/src/google.golang.org/appengine/taskqueue/ns_classic.go
deleted file mode 100644
index f8aa96e..0000000
--- a/go/src/google.golang.org/appengine/taskqueue/ns_classic.go
+++ /dev/null
@@ -1,18 +0,0 @@
-// +build appengine
-
-package taskqueue
-
-import (
- basepb "appengine_internal/base"
-
- "golang.org/x/net/context"
-
- "google.golang.org/appengine/internal"
-)
-
-func getDefaultNamespace(ctx context.Context) string {
- c := internal.ClassicContextFromContext(ctx)
- s := &basepb.StringProto{}
- c.Call("__go__", "GetDefaultNamespace", &basepb.VoidProto{}, s, nil)
- return s.GetValue()
-}
diff --git a/go/src/google.golang.org/appengine/taskqueue/ns_vm.go b/go/src/google.golang.org/appengine/taskqueue/ns_vm.go
deleted file mode 100644
index af3d841..0000000
--- a/go/src/google.golang.org/appengine/taskqueue/ns_vm.go
+++ /dev/null
@@ -1,13 +0,0 @@
-// +build !appengine
-
-package taskqueue
-
-import (
- "golang.org/x/net/context"
-
- "google.golang.org/appengine/internal"
-)
-
-func getDefaultNamespace(ctx context.Context) string {
- return internal.IncomingHeaders(ctx).Get(defaultNamespace)
-}
diff --git a/go/src/google.golang.org/appengine/taskqueue/taskqueue.go b/go/src/google.golang.org/appengine/taskqueue/taskqueue.go
index d7c7ed3..9b62fac 100644
--- a/go/src/google.golang.org/appengine/taskqueue/taskqueue.go
+++ b/go/src/google.golang.org/appengine/taskqueue/taskqueue.go
@@ -152,10 +152,18 @@
defaultNamespace = http.CanonicalHeaderKey("X-AppEngine-Default-Namespace")
)
+func getDefaultNamespace(ctx context.Context) string {
+ return internal.IncomingHeaders(ctx).Get(defaultNamespace)
+}
+
func newAddReq(c context.Context, task *Task, queueName string) (*pb.TaskQueueAddRequest, error) {
if queueName == "" {
queueName = "default"
}
+ path := task.Path
+ if path == "" {
+ path = "/_ah/queue/" + queueName
+ }
eta := task.ETA
if eta.IsZero() {
eta = time.Now().Add(task.Delay)
@@ -182,7 +190,7 @@
} else {
return nil, fmt.Errorf("taskqueue: bad method %q", method)
}
- req.Url = []byte(task.Path)
+ req.Url = []byte(path)
for k, vs := range task.Header {
for _, v := range vs {
req.Header = append(req.Header, &pb.TaskQueueAddRequest_Header{
diff --git a/go/src/google.golang.org/appengine/taskqueue/taskqueue_test.go b/go/src/google.golang.org/appengine/taskqueue/taskqueue_test.go
index 7a06aa4..0c14015 100644
--- a/go/src/google.golang.org/appengine/taskqueue/taskqueue_test.go
+++ b/go/src/google.golang.org/appengine/taskqueue/taskqueue_test.go
@@ -6,6 +6,7 @@
import (
"errors"
+ "fmt"
"reflect"
"testing"
@@ -101,3 +102,15 @@
t.Errorf("AddMulti got %v, wanted %v", err, want)
}
}
+
+func TestAddWithEmptyPath(t *testing.T) {
+ c := aetesting.FakeSingleContext(t, "taskqueue", "Add", func(req *pb.TaskQueueAddRequest, res *pb.TaskQueueAddResponse) error {
+ if got, want := string(req.Url), "/_ah/queue/a-queue"; got != want {
+ return fmt.Errorf("req.Url = %q; want %q", got, want)
+ }
+ return nil
+ })
+ if _, err := Add(c, &Task{}, "a-queue"); err != nil {
+ t.Fatalf("Add: %v", err)
+ }
+}
diff --git a/go/src/google.golang.org/appengine/urlfetch/urlfetch.go b/go/src/google.golang.org/appengine/urlfetch/urlfetch.go
index 454fac4..6ffe1e6 100644
--- a/go/src/google.golang.org/appengine/urlfetch/urlfetch.go
+++ b/go/src/google.golang.org/appengine/urlfetch/urlfetch.go
@@ -29,8 +29,7 @@
// this transport and use the Client rather than using this transport
// directly.
type Transport struct {
- Context context.Context
- Deadline time.Duration // zero means 5-second default
+ Context context.Context
// Controls whether the application checks the validity of SSL certificates
// over HTTPS connections. A value of false (the default) instructs the
@@ -47,10 +46,13 @@
// Client returns an *http.Client using a default urlfetch Transport. This
// client will have the default deadline of 5 seconds, and will check the
// validity of SSL certificates.
-func Client(context context.Context) *http.Client {
+//
+// Any deadline of the provided context will be used for requests through this client;
+// if the client does not have a deadline then a 5 second default is used.
+func Client(ctx context.Context) *http.Client {
return &http.Client{
Transport: &Transport{
- Context: context,
+ Context: ctx,
},
}
}
@@ -136,11 +138,8 @@
FollowRedirects: proto.Bool(false), // http.Client's responsibility
MustValidateServerCertificate: proto.Bool(!t.AllowInvalidServerCertificate),
}
- ctx := t.Context
-
- if t.Deadline != 0 {
- freq.Deadline = proto.Float64(t.Deadline.Seconds())
- ctx, _ = context.WithTimeout(ctx, t.Deadline)
+ if deadline, ok := t.Context.Deadline(); ok {
+ freq.Deadline = proto.Float64(deadline.Sub(time.Now()).Seconds())
}
for k, vals := range req.Header {
@@ -167,7 +166,7 @@
}
fres := &pb.URLFetchResponse{}
- if err := internal.Call(ctx, "urlfetch", "Fetch", freq, fres); err != nil {
+ if err := internal.Call(t.Context, "urlfetch", "Fetch", freq, fres); err != nil {
return nil, err
}
diff --git a/go/src/google.golang.org/appengine/user/oauth.go b/go/src/google.golang.org/appengine/user/oauth.go
index 727a080..91e840a 100644
--- a/go/src/google.golang.org/appengine/user/oauth.go
+++ b/go/src/google.golang.org/appengine/user/oauth.go
@@ -31,6 +31,7 @@
AuthDomain: *res.AuthDomain,
Admin: res.GetIsAdmin(),
ID: *res.UserId,
+ ClientID: res.GetClientId(),
}, nil
}
diff --git a/go/src/google.golang.org/appengine/user/user.go b/go/src/google.golang.org/appengine/user/user.go
index afd5459..eb76f59 100644
--- a/go/src/google.golang.org/appengine/user/user.go
+++ b/go/src/google.golang.org/appengine/user/user.go
@@ -26,6 +26,10 @@
// with a Google account, or empty otherwise.
ID string
+ // ClientID is the ID of the pre-registered client so its identity can be verified.
+ // See https://developers.google.com/console/help/#generatingoauth2 for more information.
+ ClientID string
+
FederatedIdentity string
FederatedProvider string
}
diff --git a/go/src/google.golang.org/appengine/user/user_classic.go b/go/src/google.golang.org/appengine/user/user_classic.go
index 9f37a08..a747ef3 100644
--- a/go/src/google.golang.org/appengine/user/user_classic.go
+++ b/go/src/google.golang.org/appengine/user/user_classic.go
@@ -1,3 +1,7 @@
+// Copyright 2015 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
// +build appengine
package user
diff --git a/go/src/google.golang.org/appengine/user/user_test.go b/go/src/google.golang.org/appengine/user/user_test.go
index 77b9404..5fc5957 100644
--- a/go/src/google.golang.org/appengine/user/user_test.go
+++ b/go/src/google.golang.org/appengine/user/user_test.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
+// +build !appengine
+
package user
import (
diff --git a/go/src/google.golang.org/appengine/user/user_vm.go b/go/src/google.golang.org/appengine/user/user_vm.go
index 38dc632..8dc672e 100644
--- a/go/src/google.golang.org/appengine/user/user_vm.go
+++ b/go/src/google.golang.org/appengine/user/user_vm.go
@@ -1,3 +1,7 @@
+// Copyright 2014 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
// +build !appengine
package user