Пример #1
0
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)
}
Пример #2
0
// 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,
		},
	}
}
Пример #3
0
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
}
Пример #4
0
// 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
}
Пример #5
0
// 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
}
Пример #6
0
// 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
}
Пример #7
0
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
}
Пример #8
0
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
}
Пример #9
0
// 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
}
Пример #10
0
// 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)
}
Пример #11
0
// 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
}
Пример #12
0
// 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
}
Пример #13
0
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)
}
Пример #14
0
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
}
Пример #15
0
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
}
Пример #16
0
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
}
Пример #17
0
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
}
Пример #18
0
// 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)
}
Пример #19
0
// 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)
}
Пример #20
0
// 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)
}
Пример #21
0
// 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
}
Пример #22
0
// 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
}
Пример #23
0
// 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, ""
}
Пример #24
0
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
}
Пример #25
0
// 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
}
Пример #26
0
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)
}