func (c *context) Call(service, method string, in, out ProtoMessage, opts *CallOptions) error { if service == "__go__" { if method == "GetNamespace" { out.(*basepb.StringProto).Value = proto.String(c.req.Header.Get("X-AppEngine-Current-Namespace")) return nil } if method == "GetDefaultNamespace" { out.(*basepb.StringProto).Value = proto.String(c.req.Header.Get("X-AppEngine-Default-Namespace")) return nil } } if f, ok := apiOverrides[struct{ service, method string }{service, method}]; ok { return f(in, out, opts) } data, err := proto.Marshal(in) if err != nil { return err } requestID := c.req.Header.Get("X-Appengine-Internal-Request-Id") res, err := call(service, method, data, requestID) if err != nil { return err } return proto.Unmarshal(res, out) }
// keyToProto converts a *Key to a Reference proto. func keyToProto(defaultAppID string, k *Key) *pb.Reference { appID := k.appID if appID == "" { appID = defaultAppID } n := 0 for i := k; i != nil; i = i.parent { n++ } e := make([]*pb.Path_Element, n) for i := k; i != nil; i = i.parent { n-- e[n] = &pb.Path_Element{ Type: &i.kind, } // At most one of {Name,Id} should be set. // Neither will be set for incomplete keys. if i.stringID != "" { e[n].Name = &i.stringID } else if i.intID != 0 { e[n].Id = &i.intID } } var namespace *string if k.namespace != "" { namespace = proto.String(k.namespace) } return &pb.Reference{ App: proto.String(appID), NameSpace: namespace, Path: &pb.Path{ Element: e, }, } }
func readConfig(r io.Reader) *rpb.Config { raw, err := ioutil.ReadAll(r) if err != nil { log.Fatal("appengine: could not read from stdin: ", err) } if len(raw) == 0 { // If there were zero bytes, assume this code is not being run as part of // a complete app under devappserver2, and generate some reasonable defaults. log.Print("appengine: not running under devappserver2; using some default configuration") return &rpb.Config{ AppId: []byte("dev~my-app"), VersionId: []byte("1.2345"), ApiHost: proto.String("localhost"), ApiPort: proto.Int32(1), Datacenter: proto.String("us1"), InstanceId: proto.String("deadbeef"), } } b := make([]byte, base64.StdEncoding.DecodedLen(len(raw))) n, err := base64.StdEncoding.Decode(b, raw) if err != nil { log.Fatal("appengine: could not base64 decode stdin: ", err) } config := &rpb.Config{} err = proto.Unmarshal(b[:n], config) if err != nil { log.Fatal("appengine: could not decode runtime_config: ", err) } return config }
// LoginURLFederated is like LoginURL but accepts a user's OpenID identifier. func LoginURLFederated(c appengine.Context, dest, identity string) (string, error) { req := &pb.CreateLoginURLRequest{ DestinationUrl: proto.String(dest), } if identity != "" { req.FederatedIdentity = proto.String(identity) } res := &pb.CreateLoginURLResponse{} if err := c.Call("user", "CreateLoginURL", req, res, nil); err != nil { return "", err } return *res.LoginUrl, nil }
// Put saves src to the index. If id is empty, a new ID is allocated by the // service and returned. If id is not empty, any existing index entry for that // ID is replaced. // // The ID is a human-readable ASCII string. It must contain no whitespace // characters and not start with "!". // // src must be a non-nil struct pointer. func (x *Index) Put(c appengine.Context, id string, src interface{}) (string, error) { fields, err := saveFields(src) if err != nil { return "", err } d := &pb.Document{ Field: fields, } if id != "" { if !validIndexNameOrDocID(id) { return "", fmt.Errorf("search: invalid ID %q", id) } d.Id = proto.String(id) } req := &pb.IndexDocumentRequest{ Params: &pb.IndexDocumentParams{ Document: []*pb.Document{d}, IndexSpec: &x.spec, }, } res := &pb.IndexDocumentResponse{} if err := c.Call("search", "IndexDocument", req, res, nil); err != nil { return "", err } if len(res.Status) != 1 || len(res.DocId) != 1 { return "", fmt.Errorf("search: internal error: wrong number of results (%d Statuses, %d DocIDs)", len(res.Status), len(res.DocId)) } if s := res.Status[0]; s.GetCode() != pb.SearchServiceError_OK { return "", fmt.Errorf("search: %s: %s", s.GetCode(), s.GetErrorDetail()) } return res.DocId[0], nil }
// Stat stats a file. func Stat(c appengine.Context, filename string) (os.FileInfo, error) { // The file has to be open for stat to succeed, for now. // TODO: remove this after the restriction is lifted. oreq := &filepb.OpenRequest{ Filename: &filename, ContentType: filepb.FileContentType_RAW.Enum(), OpenMode: filepb.OpenRequest_READ.Enum(), } ores := &filepb.OpenResponse{} if err := c.Call("file", "Open", oreq, ores, nil); err != nil { return nil, err } defer func() { creq := &filepb.CloseRequest{ Filename: oreq.Filename, } c.Call("file", "Close", creq, new(filepb.CloseResponse), nil) }() sreq := &filepb.StatRequest{ Filename: proto.String(filename), } sres := &filepb.StatResponse{} if err := c.Call("file", "Stat", sreq, sres, nil); err != nil { return nil, err } for _, st := range sres.Stat { if *st.Filename == filename { return fileInfo{st}, nil } } return nil, os.ErrNotExist }
func BackendHostname(c apiContext, name string, index int) string { req := &pb.GetHostnameRequest{ Module: proto.String(name), } if index != -1 { req.Instance = proto.String(strconv.Itoa(index)) } res := &pb.GetHostnameResponse{} if err := c.Call("modules", "GetHostname", req, res, nil); err != nil { log.Printf("appengine: call to modules.GetHostname (name=%s, index=%d) failed: %s", name, index, err) return "" // The API doesn't allow for error returns. } return *res.Hostname }
func send(c appengine.Context, method string, msg *Message) error { req := &pb.MailMessage{ Sender: &msg.Sender, To: msg.To, Cc: msg.Cc, Bcc: msg.Bcc, Subject: &msg.Subject, } if msg.ReplyTo != "" { req.ReplyTo = &msg.ReplyTo } if msg.Body != "" { req.TextBody = &msg.Body } if msg.HTMLBody != "" { req.HtmlBody = &msg.HTMLBody } if len(msg.Attachments) > 0 { req.Attachment = make([]*pb.MailAttachment, len(msg.Attachments)) for i, att := range msg.Attachments { req.Attachment[i] = &pb.MailAttachment{ FileName: proto.String(att.Name), Data: att.Data, } if att.ContentID != "" { req.Attachment[i].ContentID = proto.String(att.ContentID) } } } for key, vs := range msg.Headers { for _, v := range vs { req.Header = append(req.Header, &pb.MailHeader{ Name: proto.String(key), Value: proto.String(v), }) } } res := &bpb.VoidProto{} if err := c.Call("mail", method, req, res, nil); err != nil { return err } return nil }
// LogoutURL returns a URL that, when visited, signs the user out, // then redirects the user to the URL specified by dest. func LogoutURL(c appengine.Context, dest string) (string, error) { req := &pb.CreateLogoutURLRequest{ DestinationUrl: proto.String(dest), } res := &pb.CreateLogoutURLResponse{} if err := c.Call("user", "CreateLogoutURL", req, res, nil); err != nil { return "", err } return *res.LogoutUrl, nil }
// Call is an implementation of appengine.Context's Call that delegates // to a child api_server.py instance. func (c *context) Call(service, method string, in, out appengine_internal.ProtoMessage, opts *appengine_internal.CallOptions) error { if service == "__go__" && (method == "GetNamespace" || method == "GetDefaultNamespace") { out.(*basepb.StringProto).Value = proto.String("") return nil } data, err := proto.Marshal(in) if err != nil { return err } req, err := proto.Marshal(&remoteapipb.Request{ ServiceName: proto.String(service), Method: proto.String(method), Request: data, RequestId: proto.String(c.session), }) if err != nil { return err } res, err := http.Post(c.apiURL, "application/octet-stream", bytes.NewReader(req)) if err != nil { return err } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if res.StatusCode != 200 { return fmt.Errorf("got status %d; body: %q", res.StatusCode, body) } if err != nil { return err } resp := &remoteapipb.Response{} err = proto.Unmarshal(body, resp) if err != nil { return err } if e := resp.GetApplicationError(); e != nil { return fmt.Errorf("remote_api error (%v): %v", *e.Code, *e.Detail) } return proto.Unmarshal(resp.Response, out) }
// Create creates a new file, opened for append. // // The file must be closed when done writing. // // The provided filename may be absolute ("/gs/bucketname/objectname") // or may be just the filename, in which case the bucket is determined // from opts. The absolute filename is returned. func Create(c appengine.Context, filename string, opts *CreateOptions) (wc io.WriteCloser, absFilename string, err error) { reqFilename, err := opts.requestFilename(c, filename) if err != nil { return nil, "", err } req := &pb.CreateRequest{ Filename: &reqFilename, Filesystem: proto.String("gs"), ContentType: pb.FileContentType_RAW.Enum(), Parameters: []*pb.CreateRequest_Parameter{ { Name: proto.String("content_type"), Value: proto.String(opts.mime()), }, }, } res := &pb.CreateResponse{} if err := c.Call("file", "Create", req, res, nil); err != nil { return nil, "", err } w := &writer{ c: c, filename: *res.Filename, } w.bw = bufio.NewWriterSize(appendWriter{w}, writeBufferSize) oreq := &pb.OpenRequest{ Filename: res.Filename, ContentType: pb.FileContentType_RAW.Enum(), OpenMode: pb.OpenRequest_APPEND.Enum(), ExclusiveLock: proto.Bool(true), } ores := &pb.OpenResponse{} if err := c.Call("file", "Open", oreq, ores, nil); err != nil { return nil, "", err } return w, reqFilename, nil }
// Create begins creating a new blob. The provided mimeType if non-empty // is stored in the blob's BlobInfo in datastore, else defaults to // application/octet-stream. The returned Writer should be written to, // then closed, and then its Key method can be called to retrieve the // newly-created blob key if there were no errors. func Create(c appengine.Context, mimeType string) (*Writer, error) { c, _ = appengine.Namespace(c, "") // Blobstore is always in the empty string namespace if mimeType == "" { mimeType = "application/octet-stream" } req := &filepb.CreateRequest{ Filesystem: proto.String("blobstore"), ContentType: filepb.FileContentType_RAW.Enum(), Parameters: []*filepb.CreateRequest_Parameter{ { Name: proto.String("content_type"), Value: proto.String(mimeType), }, }, } res := &filepb.CreateResponse{} if err := c.Call("file", "Create", req, res, nil); err != nil { return nil, err } w := &Writer{ c: c, filename: *res.Filename, } if !strings.HasPrefix(w.filename, blobstoreFileDirectory) { return nil, errorf("unexpected filename from files service: %q", w.filename) } oreq := &filepb.OpenRequest{ Filename: res.Filename, ContentType: filepb.FileContentType_RAW.Enum(), OpenMode: filepb.OpenRequest_APPEND.Enum(), ExclusiveLock: proto.Bool(true), } ores := &filepb.OpenResponse{} if err := c.Call("file", "Open", oreq, ores, nil); err != nil { return nil, err } return w, nil }
func (n *namespacedContext) Call(service, method string, in, out appengine_internal.ProtoMessage, opts *appengine_internal.CallOptions) error { // Apply any namespace mods, but only if we have a non-empty namespace. if n.namespace != "" { if mod, ok := appengine_internal.NamespaceMods[service]; ok { mod(in, n.namespace) } } if service == "__go__" && method == "GetNamespace" { out.(*basepb.StringProto).Value = proto.String(n.namespace) return nil } return n.Context.Call(service, method, in, out, opts) }
func (r *reader) fetchBlobData(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 := r.c.Call("blobstore", "FetchData", req, res, nil); err != nil { return err } if len(res.Data) == 0 { return io.EOF } r.buf, r.r, r.off = res.Data, 0, off return nil }
func (r *reader) fetchFileData(off int64) error { req := &filepb.ReadRequest{ Filename: proto.String(r.filename), Pos: proto.Int64(off), MaxBytes: proto.Int64(readBufferSize), } res := &filepb.ReadResponse{} if err := r.c.Call("file", "Read", req, res, nil); err != nil { return err } if len(res.Data) == 0 { return io.EOF } r.buf, r.r, r.off = res.Data, 0, off return nil }
func runOnce(c appengine.Context, f func(appengine.Context) error, opts *TransactionOptions) error { // Begin the transaction. t := &transaction{Context: c} req := &pb.BeginTransactionRequest{ App: proto.String(c.FullyQualifiedAppID()), } if opts != nil && opts.XG { req.AllowMultipleEg = proto.Bool(true) } if err := t.Context.Call("datastore_v3", "BeginTransaction", req, &t.transaction, nil); err != nil { return err } // Call f, rolling back the transaction if f returns a non-nil error, or panics. // The panic is not recovered. defer func() { if t.finished { return } t.finished = true // Ignore the error return value, since we are already returning a non-nil // error (or we're panicking). c.Call("datastore_v3", "Rollback", &t.transaction, &pb.VoidProto{}, nil) }() if err := f(t); err != nil { return err } t.finished = true // Commit the transaction. res := &pb.CommitResponse{} err := c.Call("datastore_v3", "Commit", &t.transaction, res, nil) if ae, ok := err.(*appengine_internal.APIError); ok { if appengine.IsDevAppServer() { // The Python Dev AppServer raises an ApplicationError with error code 2 (which is // Error.CONCURRENT_TRANSACTION) and message "Concurrency exception.". if ae.Code == int32(pb.Error_BAD_REQUEST) && ae.Detail == "ApplicationError: 2 Concurrency exception." { return ErrConcurrentTransaction } } if ae.Code == int32(pb.Error_CONCURRENT_TRANSACTION) { return ErrConcurrentTransaction } } return err }
func (w *Writer) flush() { for len(w.buf) > 0 { chunk := w.buf if len(chunk) > maxWriteChunkSize { chunk = chunk[:maxWriteChunkSize] } req := &filepb.AppendRequest{ Filename: proto.String(w.filename), Data: chunk, } res := &filepb.AppendResponse{} if err := w.c.Call("file", "Append", req, res, nil); err != nil { w.writeErr = err return } w.buf = w.buf[len(chunk):] } w.buf = nil }
// Close flushes outstanding buffered writes and finalizes the blob. After // calling Close the key can be retrieved by calling Key. func (w *Writer) Close() (closeErr error) { defer func() { // Save the error for Key w.closeErr = closeErr }() if w.closed { return errorf("Writer is already closed") } w.closed = true w.flush() if w.writeErr != nil { return w.writeErr } req := &filepb.CloseRequest{ Filename: proto.String(w.filename), Finalize: proto.Bool(true), } res := &filepb.CloseResponse{} return w.c.Call("file", "Close", req, res, nil) }
// 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 appengine.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 := c.Call("blobstore", "CreateUploadURL", req, res, nil); err != nil { return nil, err } return url.Parse(*res.Url) }
// Get loads the document with the given ID into dst. // // The ID is a human-readable ASCII string. It must be non-empty, contain no // whitespace characters and not start with "!". // // dst must be a non-nil struct pointer. func (x *Index) Get(c appengine.Context, id string, dst interface{}) error { if id == "" || !validIndexNameOrDocID(id) { return fmt.Errorf("search: invalid ID %q", id) } req := &pb.ListDocumentsRequest{ Params: &pb.ListDocumentsParams{ IndexSpec: &x.spec, StartDocId: proto.String(id), Limit: proto.Int32(1), }, } res := &pb.ListDocumentsResponse{} if err := c.Call("search", "ListDocuments", req, res, nil); err != nil { return err } if res.Status == nil || res.Status.GetCode() != pb.SearchServiceError_OK { return fmt.Errorf("search: %s: %s", res.Status.GetCode(), res.Status.GetErrorDetail()) } if len(res.Document) != 1 || res.Document[0].GetId() != id { return ErrNoSuchDocument } return loadFields(dst, res.Document[0].Field) }
// saveFields converts from a struct pointer to protobufs. func saveFields(src interface{}) (fields []*pb.Field, err error) { v := reflect.ValueOf(src) if v.Kind() != reflect.Ptr || v.IsNil() || v.Elem().Kind() != reflect.Struct { return nil, ErrInvalidDocumentType } v = v.Elem() vType := v.Type() for i := 0; i < v.NumField(); i++ { f := v.Field(i) if !f.CanSet() { continue } fieldValue := &pb.FieldValue{} switch x := f.Interface().(type) { case string: fieldValue.Type = pb.FieldValue_TEXT.Enum() fieldValue.StringValue = proto.String(x) case Atom: fieldValue.Type = pb.FieldValue_ATOM.Enum() fieldValue.StringValue = proto.String(string(x)) case HTML: fieldValue.Type = pb.FieldValue_HTML.Enum() fieldValue.StringValue = proto.String(string(x)) case time.Time: fieldValue.Type = pb.FieldValue_DATE.Enum() fieldValue.StringValue = proto.String(strconv.FormatInt(x.UnixNano()/1e6, 10)) case float64: fieldValue.Type = pb.FieldValue_NUMBER.Enum() fieldValue.StringValue = proto.String(strconv.FormatFloat(x, 'e', -1, 64)) default: return nil, fmt.Errorf("search: unsupported field type: %v", f.Type()) } name := vType.Field(i).Name if p := fieldValue.StringValue; p != nil && !utf8.ValidString(*p) { return nil, fmt.Errorf("search: %q field is invalid UTF-8: %q", name, *p) } fields = append(fields, &pb.Field{ Name: proto.String(name), Value: fieldValue, }) } return fields, nil }
// RoundTrip issues a single HTTP request and returns its response. Per the // http.RoundTripper interface, RoundTrip only returns an error if there // was an unsupported request or the URL Fetch proxy fails. // Note that HTTP response codes such as 5xx, 403, 404, etc are not // errors as far as the transport is concerned and will be returned // with err set to nil. func (t *Transport) RoundTrip(req *http.Request) (res *http.Response, err error) { methNum, ok := pb.URLFetchRequest_RequestMethod_value[req.Method] if !ok { return nil, fmt.Errorf("urlfetch: unsupported HTTP method %q", req.Method) } method := pb.URLFetchRequest_RequestMethod(methNum) freq := &pb.URLFetchRequest{ Method: &method, Url: proto.String(urlString(req.URL)), FollowRedirects: proto.Bool(false), // http.Client's responsibility MustValidateServerCertificate: proto.Bool(!t.AllowInvalidServerCertificate), } opts := &appengine_internal.CallOptions{} if t.Deadline != 0 { freq.Deadline = proto.Float64(t.Deadline.Seconds()) opts.Timeout = t.Deadline } for k, vals := range req.Header { for _, val := range vals { freq.Header = append(freq.Header, &pb.URLFetchRequest_Header{ Key: proto.String(k), Value: proto.String(val), }) } } if methodAcceptsRequestBody[req.Method] && req.Body != nil { freq.Payload, err = ioutil.ReadAll(req.Body) if err != nil { return nil, err } } fres := &pb.URLFetchResponse{} if err := t.Context.Call("urlfetch", "Fetch", freq, fres, opts); err != nil { return nil, err } res = &http.Response{} res.StatusCode = int(*fres.StatusCode) res.Status = fmt.Sprintf("%d %s", res.StatusCode, statusCodeToText(res.StatusCode)) res.Header = make(http.Header) res.Request = req // Faked: res.ProtoMajor = 1 res.ProtoMinor = 1 res.Proto = "HTTP/1.1" res.Close = true for _, h := range fres.Header { hkey := http.CanonicalHeaderKey(*h.Key) hval := *h.Value if hkey == "Content-Length" { // Will get filled in below for all but HEAD requests. if req.Method == "HEAD" { res.ContentLength, _ = strconv.ParseInt(hval, 10, 64) } continue } res.Header.Add(hkey, hval) } if req.Method != "HEAD" { res.ContentLength = int64(len(fres.Content)) } truncated := fres.GetContentWasTruncated() res.Body = &bodyReader{content: fres.Content, truncated: truncated} return }
// valueToProto converts a named value to a newly allocated Property. // The returned error string is empty on success. func valueToProto(defaultAppID, name string, v reflect.Value, multiple bool) (p *pb.Property, errStr string) { var ( pv pb.PropertyValue unsupported bool ) switch v.Kind() { case reflect.Invalid: // No-op. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: pv.Int64Value = proto.Int64(v.Int()) case reflect.Bool: pv.BooleanValue = proto.Bool(v.Bool()) case reflect.String: pv.StringValue = proto.String(v.String()) case reflect.Float32, reflect.Float64: pv.DoubleValue = proto.Float64(v.Float()) case reflect.Ptr: if k, ok := v.Interface().(*Key); ok { if k != nil { pv.Referencevalue = keyToReferenceValue(defaultAppID, k) } } else { unsupported = true } case reflect.Struct: if t, ok := v.Interface().(time.Time); ok { if t.Before(minTime) || t.After(maxTime) { return nil, "time value out of range" } pv.Int64Value = proto.Int64(toUnixMicro(t)) } else { unsupported = true } case reflect.Slice: if b, ok := v.Interface().([]byte); ok { pv.StringValue = proto.String(string(b)) } else { // nvToProto should already catch slice values. // If we get here, we have a slice of slice values. unsupported = true } default: unsupported = true } if unsupported { return nil, "unsupported datastore value type: " + v.Type().String() } p = &pb.Property{ Name: proto.String(name), Value: &pv, Multiple: proto.Bool(multiple), } if v.IsValid() { switch v.Interface().(type) { case []byte: p.Meaning = pb.Property_BLOB.Enum() case appengine.BlobKey: p.Meaning = pb.Property_BLOBKEY.Enum() case time.Time: p.Meaning = pb.Property_GD_WHEN.Enum() } } return p, "" }
func propertiesToProto(defaultAppID string, key *Key, src <-chan Property) (*pb.EntityProto, error) { defer func() { for _ = range src { // Drain the src channel, if we exit early. } }() e := &pb.EntityProto{ Key: keyToProto(defaultAppID, key), } if key.parent == nil { e.EntityGroup = &pb.Path{} } else { e.EntityGroup = keyToProto(defaultAppID, key.root()).Path } prevMultiple := make(map[string]bool) for p := range src { if pm, ok := prevMultiple[p.Name]; ok { if !pm || !p.Multiple { return nil, fmt.Errorf("datastore: multiple Properties with Name %q, but Multiple is false", p.Name) } } else { prevMultiple[p.Name] = p.Multiple } x := &pb.Property{ Name: proto.String(p.Name), Value: new(pb.PropertyValue), Multiple: proto.Bool(p.Multiple), } switch v := p.Value.(type) { case int64: x.Value.Int64Value = proto.Int64(v) case bool: x.Value.BooleanValue = proto.Bool(v) case string: x.Value.StringValue = proto.String(v) if p.NoIndex { x.Meaning = pb.Property_TEXT.Enum() } case float64: x.Value.DoubleValue = proto.Float64(v) case *Key: if v != nil { x.Value.Referencevalue = keyToReferenceValue(defaultAppID, v) } case time.Time: if v.Before(minTime) || v.After(maxTime) { return nil, fmt.Errorf("datastore: time value out of range") } x.Value.Int64Value = proto.Int64(toUnixMicro(v)) x.Meaning = pb.Property_GD_WHEN.Enum() case appengine.BlobKey: x.Value.StringValue = proto.String(string(v)) x.Meaning = pb.Property_BLOBKEY.Enum() case []byte: x.Value.StringValue = proto.String(string(v)) x.Meaning = pb.Property_BLOB.Enum() if !p.NoIndex { return nil, fmt.Errorf("datastore: cannot index a []byte valued Property with Name %q", p.Name) } default: if p.Value != nil { return nil, fmt.Errorf("datastore: invalid Value type for a Property with Name %q", p.Name) } } if p.NoIndex { e.RawProperty = append(e.RawProperty, x) } else { e.Property = append(e.Property, x) if len(e.Property) > maxIndexedProperties { return nil, errors.New("datastore: too many indexed properties") } } } return e, nil }
// toProto converts the query to a protocol buffer. func (q *Query) toProto(dst *pb.Query, appID string) error { if q.kind == "" { if len(q.filter) != 0 { return errors.New("datastore: kindless query cannot have filters") } if len(q.order) != 0 { return errors.New("datastore: kindless query cannot have sort orders") } } if len(q.projection) != 0 && q.keysOnly { return errors.New("datastore: query cannot both project and be keys-only") } dst.Reset() dst.App = proto.String(appID) if q.kind != "" { dst.Kind = proto.String(q.kind) } if q.ancestor != nil { dst.Ancestor = keyToProto(appID, q.ancestor) } if q.projection != nil { dst.PropertyName = q.projection if q.distinct { dst.GroupByPropertyName = q.projection } } if q.keysOnly { dst.KeysOnly = proto.Bool(true) dst.RequirePerfectPlan = proto.Bool(true) } for _, qf := range q.filter { if qf.FieldName == "" { return errors.New("datastore: empty query filter field name") } p, errStr := valueToProto(appID, qf.FieldName, reflect.ValueOf(qf.Value), false) if errStr != "" { return errors.New("datastore: bad query filter value type: " + errStr) } xf := &pb.Query_Filter{ Op: operatorToProto[qf.Op], Property: []*pb.Property{p}, } if xf.Op == nil { return errors.New("datastore: unknown query filter operator") } dst.Filter = append(dst.Filter, xf) } for _, qo := range q.order { if qo.FieldName == "" { return errors.New("datastore: empty query order field name") } xo := &pb.Query_Order{ Property: proto.String(qo.FieldName), Direction: sortDirectionToProto[qo.Direction], } if xo.Direction == nil { return errors.New("datastore: unknown query order direction") } dst.Order = append(dst.Order, xo) } if q.limit >= 0 { dst.Limit = proto.Int32(q.limit) } if q.offset != 0 { dst.Offset = proto.Int32(q.offset) } dst.CompiledCursor = q.start dst.EndCompiledCursor = q.end dst.Compile = proto.Bool(true) return nil }
func handle(w http.ResponseWriter, req *http.Request) { c := appengine.NewContext(req) if !user.IsAdmin(c) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(http.StatusUnauthorized) io.WriteString(w, "You must be logged in as an administrator to access this.\n") return } if req.Header.Get("X-Appcfg-Api-Version") == "" { w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(http.StatusForbidden) io.WriteString(w, "This request did not contain a necessary header.\n") return } if req.Method != "POST" { // Response must be YAML. rtok := req.FormValue("rtok") if rtok == "" { rtok = "0" } w.Header().Set("Content-Type", "text/yaml; charset=utf-8") fmt.Fprintf(w, `{app_id: %q, rtok: %q}`, c.FullyQualifiedAppID(), rtok) return } defer req.Body.Close() body, err := ioutil.ReadAll(req.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) c.Errorf("Failed reading body: %v", err) return } remReq := &pb.Request{} if err := proto.Unmarshal(body, remReq); err != nil { w.WriteHeader(http.StatusBadRequest) c.Errorf("Bad body: %v", err) return } // Only allow datastore_v3 for now, or AllocateIds for datastore_v4. service, method := *remReq.ServiceName, *remReq.Method ok := service == "datastore_v3" || (service == "datastore_v4" && method == "AllocateIds") if !ok { w.WriteHeader(http.StatusBadRequest) c.Errorf("Unsupported RPC /%s.%s", service, method) return } rawReq := &rawMessage{remReq.Request} rawRes := &rawMessage{} err = c.Call(service, method, rawReq, rawRes, nil) remRes := &pb.Response{} if err == nil { remRes.Response = rawRes.buf } else if ae, ok := err.(*appengine_internal.APIError); ok { remRes.ApplicationError = &pb.ApplicationError{ Code: &ae.Code, Detail: &ae.Detail, } } else { // This shouldn't normally happen. c.Errorf("appengine/remote_api: Unexpected error of type %T: %v", err, err) remRes.ApplicationError = &pb.ApplicationError{ Code: proto.Int32(0), Detail: proto.String(err.Error()), } } out, err := proto.Marshal(remRes) if err != nil { // This should not be possible. w.WriteHeader(500) c.Errorf("proto.Marshal: %v", err) return } c.Infof("Spooling %d bytes of response to /%s.%s", len(out), service, method) w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Length", strconv.Itoa(len(out))) w.Write(out) }