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.
Related
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()
.
.
.
}
A project I'm working on depends on having a service hosted on Google App Engine parse from SendGrid. The following code is an example of what we're doing:
package sendgrid_failure
import (
"net/http"
"fmt"
"google.golang.org/appengine"
"google.golang.org/appengine/log"
)
func init() {
http.HandleFunc("/sendgrid/parse", sendGridHandler)
}
func sendGridHandler(w http.ResponseWriter, r *http.Request) {
ctx := appengine.NewContext(r)
err := r.ParseMultipartForm(-1)
if err != nil {
log.Errorf(ctx, "Unable to parse form: %v", err)
}
fmt.Fprint(w, "Test.")
}
When SendGrid POSTs its multipart form, the console shows similar to the following:
2018/01/04 23:44:08 ERROR: Unable to parse form: open /tmp/multipart-445139883: no file writes permitted on App Engine
App Engine doesn't allow you to read/write files, but Golang appears to need it to parse. Is there an App Engine specific library to parse multipart forms, or should we be using a different method from the standard net/http library entirely? We're using the standard go runtime.
The documentation for ParseMultipartForm says:
The whole request body is parsed and up to a total of maxMemory bytes of its file parts are stored in memory, with the remainder stored on disk in temporary files.
The server attempts to write all files to disk because the application passed -1 as maxMemory. Use a value larger than the size of the files you expect to upload.
New question: Everything I can find on Google suggests that the CloudSQL connector should be working, with the exception of the source code itself (and importing the SDK's cloudsql doesn't work). Is there some place I can find this updated library, if it exists?
---EDIT: I have my answer: it seems the problem is with CloudSQL itself rather than with the driver or something similar.---
I'm trying to access CloudSQL from a Google App Engine Go program.
I've tried both go-sql-driver/mysql and ziutek/mymysql, using Go 1.2.1 and Go 1.4.2. I've tried both the go get version of go-sql-driver and cloned it directly from Github. I've tried both the App Engine installer and the archive.
Every time I try to access a database with my application, the resulting webpage states:
cloudsql: not supported in dev yet
I've seen the other similar question here, tried everything noted there, none of it worked.
The code in question:
import (
_ "github.com/go-sql-driver/mysql"
_ "appengine/cloudsql"
"database/sql"
"net/http"
)
func adminLogin(w http.ResponseWriter, r *http.Request) {
username := formatter(r.FormValue("username"))
password := formatter(r.FormValue("password"))
db, err := sql.Open("mysql",
"root:password#cloudsql(ws-virtual-classroom:database)/logins") // And all the variations on that string I could think of...
defer db.Close()
if err != nil {
log.Print(err)
} else {
rows, err := db.Query("SELECT username FROM admin_logins WHERE username=? AND password=? LIMIT 1", username, password)
defer rows.Close()
if err != nil {
log.Print(err)
} else {
var user string
for rows.Next() {
err = rows.Scan(&user)
if err != nil {
log.Print(err)
} else {
makeCookie(w, r, user, true, true)
}
}
}
}
teachersHome(w, r)
}
The result (this is displayed in my browser after I submit the login form):
the runtime process gave a bad HTTP response: ''
2015/05/17 01:53:06 cloudsql: not supported in dev yet
2015/05/17 01:53:06 http: panic serving 127.0.0.1:56970: runtime error: invalid memory address or nil pointer dereference
goroutine 12 [running]:
net/http.func·011()
/tmp/appengine/go_appengine/goroot/src/net/http/server.go:1130 +0xbb
database/sql.(*Rows).Close(0x0, 0x0, 0x0)
/tmp/appengine/go_appengine/goroot/src/database/sql/sql.go:1659 +0x31
main57750.adminLogin(0x7f76a72ef5d8, 0xc208045860, 0xc2080c4820)
main.go:208 +0x25a
net/http.HandlerFunc.ServeHTTP(0x927c78, 0x7f76a72ef5d8, 0xc208045860, 0xc2080c4820)
/tmp/appengine/go_appengine/goroot/src/net/http/server.go:1265 +0x41
github.com/gorilla/mux.(*Router).ServeHTTP(0xc20800c730, 0x7f76a72ef5d8, 0xc208045860, 0xc2080c4820)
/home/daniel/go/src/github.com/gorilla/mux/mux.go:98 +0x297
net/http.(*ServeMux).ServeHTTP(0xc20803a6f0, 0x7f76a72ef5d8, 0xc208045860, 0xc2080c4820)
/tmp/appengine/go_appengine/goroot/src/net/http/server.go:1541 +0x17d
appengine_internal.handleFilteredHTTP(0x7f76a72ef5d8, 0xc208045860, 0xc2080c4820)
/tmp/appengine/go_appengine/goroot/src/appengine_internal/api_dev.go:98 +0x413
net/http.HandlerFunc.ServeHTTP(0x927248, 0x7f76a72ef5d8, 0xc208045860, 0xc2080c4820)
/tmp/appengine/go_appengine/goroot/src/net/http/server.go:1265 +0x41
net/http.serverHandler.ServeHTTP(0xc208042540, 0x7f76a72ef5d8, 0xc208045860, 0xc2080c4820)
/tmp/appengine/go_appengine/goroot/src/net/http/server.go:1703 +0x19a
net/http.(*conn).serve(0xc2080457c0)
/tmp/appengine/go_appengine/goroot/src/net/http/server.go:1204 +0xb57
created by net/http.(*Server).Serve
/tmp/appengine/go_appengine/goroot/src/net/http/server.go:1751 +0x35e
This is when running it with goapp serve. If I deploy it I seem to have the same problem--the resulting page is blank rather than showing that text, but the log error messages are the same.
The updated library is here: https://godoc.org/google.golang.org/appengine. It doesn't have CloudSQL though. Perhaps you should just use a regular mysql connection locally:
func dialSQL() (*sql.DB, error) {
if appengine.IsDevAppServer() {
// or sql.Open("mysql", "user-name:password#ip-address-of-google-cloud-sql-instance/dbname")
return sql.Open("mysql", "user:password#/dbname")
}
return sql.Open("mysql", "cloudsql:my-instance*dbname/user/passwd")
}
Using a local database is usually the best option because connecting to a live database can be super dangerous. For example it's all too easy to accidentally run your tests against your production database and drop everything.
Nevertheless, Google has instructions for connecting to CloudSQL here: https://cloud.google.com/sql/docs/introduction. The instructions for 3rd party tools will work for Go too.
I don't think cloudsql does anything? The source just looks like it returns that error no matter what lol
https://code.google.com/p/appengine-go/source/browse/appengine/cloudsql/cloudsql.go
// Dial connects to the named Cloud SQL instance.
func Dial(instance string) (net.Conn, error) {
return connect(instance)
}
https://code.google.com/p/appengine-go/source/browse/appengine/cloudsql/cloudsql_dev.go
func connect(instance string) (net.Conn, error) {
return nil, errors.New("cloudsql: not supported in dev yet")
}
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.
Google's docs omit the most important aspect: How a Task is created. Can anybody complete the sample code:
import (
"appengine/datastore"
"appengine/taskqueue"
)
func f(c appengine.Context) {
err := datastore.RunInTransaction(c, func(c appengine.Context) error {
t := ... // WHY DOES GOOGLE NOT EXPLAIN THIS PART???
// Use the transaction's context when invoking taskqueue.Add.
_, err := taskqueue.Add(c, t, "")
// ...
})
// ...
}
I think what you need is described in the docs for datastore transactions.
So the missing code to create a task is:
t := &taskqueue.Task{Path: "/path/to/workertask"}
The reference for the Task type shows that Task is a struct with 10 or so fields, so you probably don't want to construct a Task yourself. However, it also provides the NewPOSTTask function (just below that):
NewPOSTTask creates a Task that will POST to a path with the given form data
I agree the documentation could be much better though.