Is there a replacement for appengine/user in Go 1.12? - google-app-engine

Since migrating to Go 1.12 from 1.9, appengine/user.Current() returns an empty User
Originally set this up using this tutorial: https://cloud.google.com/appengine/docs/standard/go/users/
I've tried both the recommended change to use http.Request.Context() instead of appengine.NewContext() and still trying appengine.NewContext() but now calls to Current() return an empty user object (not nil, empty)
I plan on moving to a different authentication method since it's recomended to move away from the appengine package, but this was a "quick and dirty" way to secure the app for now as this only has two users (my wife and me) for the moment.
func getContext(r *http.Request) error {
ctx = r.Context()
var w http.ResponseWriter
cu, err := Current()
.
.
.
}

Related

Google Appengine: connect to datastore without request

In all tutorials on using datastore with appengine standard go environment suggest that I should obtain appengine context from the http request and use this context when manipulating the datastore. Golang appengine datastore introduction
This is unfortunate because it makes it hard for me to use dependency injection of data services into my controllers (handlers). I can't create one instance of aservice while initializing the controller, but I have to pass requests/contexts to every data related operation.
func createHandler(dbService db.DBService) http.HandleFunc
return func (req http.Request, resp http.Response) {
entity := bindEntity(req)
/*
I undertand that I can pass whole request to the dbService
to at least remove dependency on datastore from this handler
but it doesn't seem right to pass request everywhere
*/
ctx := appengine.NewContext(req)
dbService.StoreEntity(ctx, entity)
}
}
Is there a way of getting the appengine context from somewhere else except for request? Or is there some common known design pattern how to separate the responsibility of handling request and manipulating data in app engine?
dbService := db.CreateServcie(somehowObtainedContext)
func createHandler(dbService db.DBService) http.HandleFunc
return func (req http.Request, resp http.Response) {
entity := bindEntity(req)
dbService.StoreEntity(entity)
}
}
This would make the code clearer and testing easier.

Cross platform go code for appengine

What is the GO appropriate way to create a FetchUrl/GetURL function that works from the command line and works from google app engine with its custom way to fetch a url.
I have basic code that fetches and processes some data on a URL. I want to be able to call it from code I use on my desktop, and code deployed to app engine.
Hopefully thats clear, if not please let me know and Ill clarify.
If you have some code which works both on local machine and on AppEngine environment, you have nothing to do.
If you need to do something which should or must be done differently on AppEngine, then you need to detect the environment and write different code for the different environments.
This detection and code selection is easiest done using build constraints. You can put a special comment line in the beginning of your .go file, and it may or may not be compiled and run depending on the environment.
Quoting from The Go Blog: The App Engine SDK and workspaces (GOPATH):
The App Engine SDK introduces a new build constraint term: "appengine". Files that specify
// +build appengine
will be built by the App Engine SDK and ignored by the go tool. Conversely, files that specify
// +build !appengine
are ignored by the App Engine SDK, while the go tool will happily build them.
So for example you can have 2 separate .go files, one for AppEngine and one for local (non-AppEngine) environment. Define the same function in both (with same parameter list), so no matter in which environment the code is built, the function will have one declaration. We will use this signature:
func GetURL(url string, r *http.Request) ([]byte, error)
Note that the 2nd parameter (*http.Request) is only required for the AppEngine (in order to be able to create a Context), so in the implementation for local env it is not used (can even be nil).
An elegant solution can take advantage of the http.Client type which is available in both the standard environment and in AppEngine, and which can be used to do an HTTP GET request. An http.Client value can be acquired differently on AppEngine, but once we have an http.Client value, we can proceed the same way. So we will have a common code that receives an http.Client and can do the rest.
Example implementation can look like this:
url_local.go:
// +build !appengine
package mypackage
import (
"net/http"
)
func GetURL(url string, r *http.Request) ([]byte, error) {
// Local GetURL implementation
return GetClient(url, &http.Client{})
}
url_gae.go:
// +build appengine
package mypackage
import (
"google.golang.org/appengine"
"google.golang.org/appengine/urlfetch"
"net/http"
)
func GetURL(url string, r *http.Request) ([]byte, error) {
// Appengine GetURL implementation
ctx := appengine.NewContext(r)
c := urlfetch.Client(ctx)
return GetClient(url, c)
}
url_common.go:
// No build constraint: this is common code
package mypackage
import (
"net/http"
)
func GetClient(url string, c *http.Client) ([]byte, error) {
// Implementation for both local and AppEngine
resp, err := c.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return body, nil
}
You could get some clues in the golang/appengine project itself.
For instance, its remote_api/client.go provides the client for connecting remotely to a user's production application.

c.Infof undefined (type context.Context has no field or method Infof) google.golang.org/appengine/log error

In the Go Runtime i used the method c.Infof to log messages , but it fails to compile with the following error
c.Infof undefined (type context.Context has no field or method Infof) .
The Error clearly tells that the app engine context returned from c := appengine.NewContext(r) is of type context.Context and it doesnt have a method c.Infof on it. But contrary to this the documentation in https://godoc.org/google.golang.org/appengine/log suggests that this method exists . Another point to note , The method existed on the context returned by "appengine" (import "appengine" ) package , and this doesnt seem to exist on the context returned by the new package google.golang.org/appengine , what is c.Infof equivalent on the new Context of type context.Context returned by package "google.golang.org/appengine" ?
The example in the package documentation is not correct.
Use the log package functions to write to the App Engine log. Here's the corrected example:
c := appengine.NewContext(r)
query := &log.Query{
AppLogs: true,
Versions: []string{"1"},
}
for results := query.Run(c); ; {
record, err := results.Next()
if err == log.Done {
log.Infof(c, "Done processing results")
break
}
if err != nil {
log.Errorf(c, "Failed to retrieve next log: %v", err)
break
}
log.Infof(c, "Saw record %v", record)
}
The example in the package documentation was copied from the App Engine Classic package, but not updated to use the new functions. I suggest reporting this to the App Engine Team.

Get tab information from Google Chrome in C/Go

I'd like to gather some info on Google Chrome when it's running. Currently I'm doing this using some applescript (https://gist.github.com/jcla1/6525572), but I'd like to rewrite this in C or Go.
Does anyone know of an API Chrome exposes to gather information like:
# open tabs
current active URL
So far I've only found out that I can get the current tab's title using CGWindowListCopyWindowInfo (Carbon), which works well but obviously doesn't provide the URL (and not the # open tabs, etc.).
Ideally Chrome would have a event architecture which I could hook into and receive all the events to do with entering a new URL.
P.S. Only required to work on OSX!
I have written Go programs to remotely control/inspect chrome using the Remote Debugging Protocol. The way it works is you first fetch JSON data from a specific url and it gives the open tabs and basic information such as the tab's current url. You can then use the websocket links in the JSON object to contorl/inspect individual tabs.
Since you only want basic information, you can ignore most of the debugging api and just download the JSON index. Frist, start chrome using chrome --remote-debugging-port=9222. Note that all chrome windows need to be closed for this to work. You may also use --user-data-dir=<some dir> to launch with a fresh profile so you can leave your other chrome windows open.
Once you have the port open, obtaining the data is easy. Just go to http://localhost:9222/json and unmarshal the data. Here is the code I used :
type Tab struct {
Description string `json:"description"`
DevtoolsFrontendUrl string `json:"devtoolsFrontendUrl"`
FaviconUrl string `json:"faviconUrl"`
Id string `json:"id"`
ThumbnailUrl string `json:"thumbnailUrl"`
Title string `json:"title"`
Type string `json:"type"`
Url string `json:"url"`
WebSocketDebuggerUrl string `json:"webSocketDebuggerUrl"`
}
func GetTabs() ([]Tab, error) {
resp, err := http.Get("http://localhost:9222/json")
if err != nil {
return nil, err
}
defer resp.Body.Close()
var tabs []Tab
err = json.NewDecoder(resp.Body).Decode(&tabs)
if err != nil {
return nil, err
}
return tabs, nil
}
You can obtain more information here.
There is unfortunately no way to receive an event when a new tab is opened using this API. However, you can get notified when a tab you are connected to with websockets changes in any way. If you are willing to go crazy, you can build an extension to monitor for changes such as new tabs and loaded urls.

Upload file in GAE Go

I am trying to upload a file in my GAE app. How do I the upload of a file in Google App Engine using Go and using the r.FormValue()?
You have to go through the Blobstore Go API Overview to get an idea and there is a full example on how could you store & serve user data on Google App Engine using Go.
I would suggest you to do that example in a completely separate application, so you'll be able to experiment with it for a while before trying to integrate it to your already existing one.
I managed to solve my problem by using the middle return param, "other". These code below are inside the upload handler
blobs, other, err := blobstore.ParseUpload(r)
Then assign corresponding formkey
file := blobs["file"]
**name := other["name"]** //name is a form field
**description := other["description"]** //descriptionis a form field
And use it like this in my struct value assignment
newData := data{
Name: **string(name[0])**,
Description: **string(description[0])**,
Image: string(file[0].BlobKey),
}
datastore.Put(c, datastore.NewIncompleteKey(c, "data", nil), &newData )
Not 100% sure this is the right thing but this solves my problem and it is now uploading the image to blobstore and saving other data and blobkey to datastore.
Hope this could help others too.
I have tried the full example from here https://developers.google.com/appengine/docs/go/blobstore/overview, and it worked fine doing the upload in blobstore and serving it.
But inserting extra post values to be saved somewhere in the datastore erases the values of "r.FormValue() "? Please refer to the code below
func handleUpload(w http.ResponseWriter, r *http.Request) {
c := appengine.NewContext(r)
//tried to put the saving in the datastore here, it saves as expected with correct values but would raised a server error.
blobs, _, err := blobstore.ParseUpload(r)
if err != nil {
serveError(c, w, err)
return
}
file := blobs["file"]
if len(file) == 0 {
c.Errorf("no file uploaded")
http.Redirect(w, r, "/", http.StatusFound)
return
}
// a new row is inserted but no values in column name and description
newData:= data{
Name: r.FormValue("name"), //this is always blank
Description: r.FormValue("description"), //this is always blank
}
datastore.Put(c, datastore.NewIncompleteKey(c, "Data", nil), &newData)
//the image is displayed as expected
http.Redirect(w, r, "/serve/?blobKey="+string(file[0].BlobKey), http.StatusFound)
}
Is it not possible to combine the upload with regular data? How come the values of r.FormValue() seems to disappear except for the file (input file type)? Even if I would have to force upload first before associating the blobkey, as the result of the upload, to other data, it would not be possible since I could not pass any r.FormValue() to the upload handler(which like I said becomes empty, or would raised an error when accessed prior the blobs, _, err := blobstore.ParseUpload(r) statement). I hope someone could help me solve this problem. Thank you!
In addition to using the Blobstore API, you can just use the Request.FormFile() method to get the file upload content. Use net\http package documentation for additional help.
Using the Request directly allows you to skip setting up an blobstore.UploadUrl() before handling the upload POST message.
A simple example would be:
func uploadHandler(w http.ResponseWriter, r *http.Request) {
// Create an App Engine context.
c := appengine.NewContext(r)
// use FormFile()
f, _, err := r.FormFile("file")
if err != nil {
c.Errorf("FormFile error: %v", err)
return
}
defer f.Close()
// do something with the file here
c.Infof("Hey!!! got a file: %v", f)
}

Resources