How to set session variable in golang gorilla framework? - google-app-engine

My following code
package inqzincrm
import (
"github.com/gorilla/pat"
"github.com/gorilla/sessions"
"net/http"
)
var store = sessions.NewCookieStore([]byte("X12h8v6BZC4QJl53KfNLshtr85gkC5OZ"), []byte("X12h8vasdf6BZC4QJl53KfNLshtr85gk"))
func init() {
r := pat.New()
r.Get("/", Home)
http.Handle("/", r)
}
and in handler,
package inqzincrm
import (
"appengine"
"html/template"
"net/http"
)
var aTmplt = template.Must(template.ParseFiles(
"inqzincrm/templates/base.html",
"inqzincrm/templates/index.html",
))
func Home(w http.ResponseWriter, r *http.Request) {
c := appengine.NewContext(r)
session, err := store.Get(r, "x")
c.Infof("HOST: %s", r.Host)
if session.IsNew {
session.Options.Domain = r.Host
session.Options.Path = "/"
session.Options.MaxAge = 0
session.Options.HttpOnly = false
session.Options.Secure = false
}
if err != nil {
c.Infof("Error getting session: %v", err)
}
c.Infof("Requested URL: %v", session.Values["foo"])
session.Values["foo"] = "asdf"
if err := aTmplt.ExecuteTemplate(w, "index.html", nil); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
session.Save(r, w)
}
do not set any cookie on browser side. how ever result and err are nil indicating no problem with the functions.
How I should debug further ?
I am using Ubuntu 64 bit, Google App Engine, Go with Gorilla tool kit.

Have a look at my answer to a previous question here.
In short form:
import (
"github.com/gorilla/sessions"
"net/http"
)
// Authorization Key
var authKey = []byte("somesecret")
// Encryption Key
var encKey = []byte("someothersecret")
var store = sessions.NewCookieStore(authKey, encKey)
func initSession(r *http.Request) *sessions.Session {
session, _ := store.Get(r, "my_cookie") // Don't ignore the error in real code
if session.IsNew { //Set some cookie options
session.Options.Domain = "example.org"
session.Options.MaxAge = 0
session.Options.HttpOnly = false
session.Options.Secure = true
}
return session
}
Then, in your handlers:
func ViewPageHandler(w http.ResponseWriter, r *http.Request) {
session := initSession(r)
session.Values["page"] = "view"
session.Save(r, w)
....
Your code seems to do the same thing, so without more of an example I can't see any problem with it. I can say that the example I've posted is (very slightly modified) from a working server.

the code session.save() should come before template execution code. it has been mentioned no where. but that was the whole mistake.

Related

Google Cloud SQL: driver: bad connection although different projects work

I am currently trying to deploy another app on GAE using a Google Cloud SQL instance. I already have 2 services running on there with the same setup also using a Cloudsql instance. However, when I am trying to connect to Cloudsql from my newest service, I am getting a "driver: bad connection" error and I can not figure out why. I am using the _ "github.com/go-sql-driver/mysql" driver as follows:
var (
DB_INSTANCE *sql.DB
)
...
configFile := GetConfig()
user := configFile.DatabaseReaderHost.DatabaseUser
password := configFile.DatabaseReaderHost.DatabasePassword
dbName := configFile.DatabaseReaderHost.DatabaseName
connectionName := "project-id:region:instance-name"
if appengine.IsDevAppServer() {
db, err := sql.Open("mysql", fmt.Sprintf("%s:%s#tcp(%s:%s)/%s?parseTime=true",
user,
password,
configFile.DatabaseReaderHost.DatabaseHost,
configFile.DatabaseReaderHost.DatabasePort,
dbName))
if err != nil {
panic(err)
}
DB_INSTANCE = db
return nil
} else {
dbn, err := sql.Open("mysql", fmt.Sprintf("%s:%s#cloudsql(%s)/%s", user, password, connectionName, dbName))
if err != nil {
panic(err)
}
DB_INSTANCE = dbn
return nil
}
What I noticed is that the connection to the Cloudsql instance seems to be established since the above code is not what produces the error, but my test query agains the database.
func TestDatabaseHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
var name string
err := DB_INSTANCE.QueryRow("SELECT name FROM names WHERE id = ?", 1).Scan(&name)
if err != nil {
if err == sql.ErrNoRows {
PrintSingleResponseJson(w, "error", fmt.Sprintf("no rows found"))
} else {
Log(r, fmt.Sprintf("Could not connect to database: %v", err))
PrintSingleResponseJson(w, "error", fmt.Sprintf("could not connect to database: %s", err))
}
return
}
PrintSingleResponseJson(w, "success", fmt.Sprintf("%s", name))
}
I appreciate all help.

App engine push task always returns 404 in test

I've got a push task queue in a Go App Engine application. When we try to enqueue tasks in testing for whatever reason the tasks always return 404.
Our app.yaml:
runtime: go
api_version: go1.9
handlers:
- url: /worker/.*
script: _go_app
login: admin
- url: /.*
script: _go_app
The actual task invocation:
func Handler(w http.ResponseWriter, r *http.Request) {
ctx := appengine.NewContext(r)
t := taskqueue.NewPOSTTask("/worker", map[string][]string{"key": {"val"}})
_, err := taskqueue.Add(ctx, t, "")
if err != nil {
log.Errorf(ctx, "Failed to add task");
}
fmt.Fprintf(w, "Success");
}
A still-incomplete handler, but it exists!
func Worker(w http.ResponseWriter, r *http.Request) {
ctx := appengine.NewContext(r)
log.Infof(ctx, "Worker succeeded")
}
and finally, proof that we actually added the path to our router:
func init() {
http.HandleFunc("/", Handler)
http.HandleFunc("/worker", Worker)
}
When we actually run tests, we always get the following logging output:
INFO 2018-05-03 09:51:11,794 module.py:846] default: "POST /worker HTTP/1.1" 404 19
WARNING 2018-05-03 09:51:11,794 taskqueue_stub.py:2149] Task � failed to execute. This task will retry in 0.100 seconds
INFO 2018-05-03 09:51:11,897 module.py:846] default: "POST /worker HTTP/1.1" 404 19
WARNING 2018-05-03 09:51:11,897 taskqueue_stub.py:2149] Task � failed to execute. This task will retry in 0.200 seconds
INFO 2018-05-03 09:51:12,101 module.py:846] default: "POST /worker HTTP/1.1" 404 19
WARNING 2018-05-03 09:51:12,101 taskqueue_stub.py:2149] Task � failed to execute. This task will retry in 0.400 seconds
Note that the /worker endpoint returns 302 when I try to ping it via an API client like Paw, so the route seems to have been configured correctly. The 404 only arises when I try to run things in a test.
Why is this returning 404? I've tried running tests around the example push queue in their documentation have run into the same issue there - is there some sort of missing configuration flag I'm failing to pass to goapp?
I've pushed up a GitHub repo with a minimal replicable example here
The order for running goapp is top-bottom but you need to be specific in your app.yaml. In your case this will works:
package main
import (
"fmt"
"net/http"
"google.golang.org/appengine"
"google.golang.org/appengine/log"
"google.golang.org/appengine/taskqueue"
)
func Handler(w http.ResponseWriter, r *http.Request) {
ctx := appengine.NewContext(r)
t := taskqueue.NewPOSTTask("/worker", map[string][]string{"key": {"val"}})
_, err := taskqueue.Add(ctx, t, "")
if err != nil {
log.Errorf(ctx, "Failed to add task")
}
fmt.Fprintf(w, "Success")
}
func Worker(w http.ResponseWriter, r *http.Request) {
ctx := appengine.NewContext(r)
log.Infof(ctx, "Worker succeeded")
}
func init() {
http.HandleFunc("/", Handler)
http.HandleFunc("/worker", Worker)
}
For this you need to map url like:
runtime: go
api_version: go1.9
handlers:
- url: /worker
script: _go_app
login: admin
- url: /.*
script: _go_app
The result is:
See that worker is running two times. This is ocurring because GET /favicon.ico is entering in GET /.* mapping. So this is only details for you!
UPDATE (05/14/2018):
In your test you use aetest.NewInstance(), that runs dev_appserver.py in ioutil.TempDir("", "appengine-aetest"), that writes your own main.go and app.yaml. See above in instance_vm.go:
i.appDir, err = ioutil.TempDir("", "appengine-aetest")
if err != nil {
return err
}
defer func() {
if err != nil {
os.RemoveAll(i.appDir)
}
}()
err = os.Mkdir(filepath.Join(i.appDir, "app"), 0755)
if err != nil {
return err
}
err = ioutil.WriteFile(filepath.Join(i.appDir, "app", "app.yaml"), []byte(i.appYAML()), 0644)
if err != nil {
return err
}
err = ioutil.WriteFile(filepath.Join(i.appDir, "app", "stubapp.go"), []byte(appSource), 0644)
if err != nil {
return err
}
//... others codes
const appYAMLTemplate = `
application: %s
version: 1
runtime: go
api_version: go1
vm: true
handlers:
- url: /.*
script: _go_app
`
const appSource = `
package main
import "google.golang.org/appengine"
func main() { appengine.Main() }
`
So you need to create your own server-instance. This is a way:
//out buffer
var out bytes.Buffer
//start server
c := exec.Command("goapp", "serve") //default port=8080, adminPort=8000
c.Stdout = &out
c.Stderr = &out
c.Start()
defer c.Process.Kill()
//delay to wait server is completed
time.Sleep(10 * time.Second)
//... others codes
//quit server
quitReq, err := http.NewRequest("GET", "http://localhost:8000/quit", nil)
_, err := client.Do(quitReq)
if err != nil {
fmt.Errorf("GET /quit handler error: %v", err)
}
To test your Handler function do:
//create request (testing Handler func)
req, err := http.NewRequest("GET", "http://localhost:8080/", nil)
if err != nil {
t.Fatal(err.Error())
}
//do GET
client := http.DefaultClient
resp, err := client.Do(req)
if err != nil {
t.Error(err)
}
defer resp.Body.Close()
//delay to wait for the worker to execute
time.Sleep(10 * time.Second)
To retrieve the result and test it:
//read response
b, _ := ioutil.ReadAll(resp.Body)
resp_content := string(b)
//checking
if !strings.Contains(resp_content, "Handler Success") {
t.Errorf("Handler not working")
}
//log server content
logserver := out.String()
if !strings.Contains(logserver, "Worker succeeded") {
t.Errorf("Worker not working")
}
//log response
t.Logf(logserver)
The result is:
UPDATE: Link for Github: https://github.com/ag-studies/go-appengine-sample
Hope this help!!
Your handlers pattern is /worker/.* but you are issuing tasks to /worker.
You should do 1 of the following:
Either change pattern to /worker.* or just /worker
Or issue request(s) to /worker/ or /worker/some-task-name.

AppEngine Blobstore keeps returning 206 partial response

I am having trouble getting the AppEngine Blobstore upload sample to work. Looking further, I see that Blobstore.Send() always returns HTTP 206 "Partial response".
package blobstore_example
import (
"html/template"
"io"
"net/http"
"appengine"
"appengine/blobstore"
)
func serveError(c appengine.Context, w http.ResponseWriter, err error) {
w.WriteHeader(http.StatusInternalServerError)
w.Header().Set("Content-Type", "text/plain")
io.WriteString(w, "Internal Server Error")
c.Errorf("%v", err)
}
var rootTemplate = template.Must(template.New("root").Parse(rootTemplateHTML))
const rootTemplateHTML = `
<html><body>
<form action="{{.}}" method="POST" enctype="multipart/form-data">
Upload File: <input type="file" name="file"><br>
<input type="submit" name="submit" value="Submit">
</form></body></html>
`
func handleRoot(w http.ResponseWriter, r *http.Request) {
c := appengine.NewContext(r)
uploadURL, err := blobstore.UploadURL(c, "/upload", nil)
if err != nil {
serveError(c, w, err)
return
}
w.Header().Set("Content-Type", "text/html")
err = rootTemplate.Execute(w, uploadURL)
if err != nil {
c.Errorf("%v", err)
}
}
func handleServe(w http.ResponseWriter, r *http.Request) {
blobstore.Send(w, appengine.BlobKey(r.FormValue("blobKey")))
}
func handleUpload(w http.ResponseWriter, r *http.Request) {
c := appengine.NewContext(r)
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
}
http.Redirect(w, r, "/serve/?blobKey="+string(file[0].BlobKey), http.StatusFound)
}
func init() {
http.HandleFunc("/", handleRoot)
http.HandleFunc("/serve/", handleServe)
http.HandleFunc("/upload", handleUpload)
}
Firefox 32 does not play this and says:
Video can't be played because the file is corrupt.
Yet, if I use wget to save the file I get the exact mp4 file with all bytes included - and local player plays the mp4 fine.
Capturing the HTTP packets I see that Firefox is not asking for a byte-range in the HTTP Request - yet the Blobstore response includes:
Content-Range: bytes 0-245778/245779\r\n
(which by the way is the full size of the mp4 file).
So why does Blobstore think it has to return HTTP 206? ...and how to get it not to?

Is it possible to use sessionauth of martini with Datastore on google app engine?

I tried to use the example of sessionauth of martini on google app engine and want to save login list in Datastore, but did not know how to deal with appengine.Context. Does anyone has the experience?
Thank you.
Update:
// Auth example is an example application which requires a login
// to view a private link. The username is "testuser" and the password
// is "password". This will require GORP and an SQLite3 database.
package ahl
import (
//"fmt"
"github.com/go-martini/martini"
"github.com/hnakamur/gaesessions"
"github.com/martini-contrib/binding"
"github.com/martini-contrib/render"
"github.com/martini-contrib/sessionauth"
"github.com/martini-contrib/sessions"
"net/http"
)
//var namespace string = "ahl"
func init() {
//store := sessions.NewCookieStore([]byte("secret123"))
store := gaesessions.NewDatastoreStore("", gaesessions.DefaultNonPersistentSessionDuration)
m := martini.Classic()
m.Use(render.Renderer())
// Default our store to use Session cookies, so we don't leave logged in
// users roaming around
//store.Options(sessions.Options{
// MaxAge: 0,
//})
m.Use(sessions.Sessions("my_session", store))
m.Use(sessionauth.SessionUser(GenerateAnonymousUser))
sessionauth.RedirectUrl = "/new-login"
sessionauth.RedirectParam = "new-next"
m.Get("/", func(r render.Render) {
r.HTML(200, "index", nil)
})
m.Get("/new-login", func(r render.Render) {
r.HTML(200, "login", nil)
})
m.Post("/new-login", binding.Bind(MyUserModel{}), func(session sessions.Session, postedUser MyUserModel, r render.Render, req *http.Request) {
// You should verify credentials against a database or some other mechanism at this point.
// Then you can authenticate this session.
//user := MyUserModel{}
user := MyUserModel{1, "testuser", "password", false}
//err := dbmap.SelectOne(&user, "SELECT * FROM users WHERE username = $1 and password = $2", postedUser.Username, postedUser.Password)
//if err != nil {
// r.Redirect(sessionauth.RedirectUrl)
// return
//} else {
err := sessionauth.AuthenticateSession(session, &user)
if err != nil {
r.JSON(500, err)
}
params := req.URL.Query()
redirect := params.Get(sessionauth.RedirectParam)
r.Redirect(redirect)
return
//}
})
m.Get("/private", sessionauth.LoginRequired, func(r render.Render, user sessionauth.User) {
r.HTML(200, "private", user.(*MyUserModel))
})
m.Get("/logout", sessionauth.LoginRequired, func(session sessions.Session, user sessionauth.User, r render.Render) {
sessionauth.Logout(session, user)
r.Redirect("/")
})
http.Handle("/", m)
}
Yes, it should be possible. The sessionauth package requires you to pass it a *sessions.Store, and there is a gaesessions package that can replace the default cookie/file stores: https://github.com/hnakamur/gaesessions
The sessionauth package has a full example (https://github.com/martini-contrib/sessionauth/blob/master/example/auth_example.go) - just replace sessions.NewCookieStore with gaesessions.NewDatastoreStore.

go gorilla/sessions angularjs and path, session values not saved (find the bug/place the blame)

Ok where to start...
The problem is when I set the session's Path to "/" the session doesn't get saved.
And I set Path because when posting to a path that is not the path where the session gets saved, aka session.Save() is called the session value "user" is empty|nil|not set.
So I set Path: "/", but the session isn't saved. When checking Chromium I see that the cookie is set. I don't know where the problem is. Is it in gorilla/sessions? Is it in AngularJS? HTML5 mode is off in angular.
So to rephrase, this happens because /api/1.0/community is a different path than /api/1.0/user where the sessions.Save(r,w) function is called and that's why I set Path: "/", . But when Path is "/" the session value "user" isn't saved.
main.go
var (
sessionStore *sessions.CookieStore
sessionAuthKey []byte = make([]byte, 64)
sessionCryptKey []byte = make([]byte, 32)
router *mux.Router = mux.NewRouter()
)
func init() {
// Generate Session Secret
sessionAuthKey = securecookie.GenerateRandomKey(64)
sessionCryptKey = securecookie.GenerateRandomKey(32)
// Create Session
sessionStore = sessions.NewCookieStore(sessionAuthKey, sessionCryptKey)
sessionStore.Options = &sessions.Options{
Domain: ".mango.dev",
Path: "/",
MaxAge: 0,
}
}
func main() {
api := router.PathPrefix("/api/1.0").Subrouter()
api.HandleFunc("/user/register", UserRegisterHandler).Methods("POST")
api.HandleFunc("/user/authenticate", UserAuthenticateHandler).Methods("POST")
api.HandleFunc("/user/endsession", UserLogoutHandler).Methods("POST")
api.HandleFunc("/user/profile", UserProfileHandler).Methods("GET")
api.HandleFunc("/user/profile", UserUpdateProfileHandler).Methods("POST")
api.HandleFunc("/user/reset_request", UserResetRequestHandler).Methods("POST")
api.HandleFunc("/user/reset_password", UserResetPasswordHandler).Methods("POST")
api.HandleFunc("/user/loginstatus", UserLoginStatusHandler).Methods("GET")
api.HandleFunc("/forums/directory", ForumsDirectoryHandler).Methods("GET")
api.HandleFunc("/community/list", CommunityListHandler).Methods("GET")
api.HandleFunc("/community/show", CommunityShowHandler).Methods("GET")
api.HandleFunc("/community/create", CommunityCreateHandler).Methods("POST")
api.HandleFunc("/community/edit", CommunityEditHandler).Methods("GET")
static := router.PathPrefix("/").Subrouter()
static.Methods("GET").Handler(http.FileServer(http.Dir("webapp/public")))
go func() {
if err := http.ListenAndServe(":8080", Log(router)); err != nil {
log.Fatal(err)
}
}()
if err := http.ListenAndServeTLS(":8443", "ssl/mango.dev.crt", "ssl/mango.dev.pem", Log(router)); err != nil {
log.Fatal(err)
}
}
handlers.go
func UserAuthenticateHandler(w http.ResponseWriter, r *http.Request) {
// ...
if valid {
tu.Name = user.UserProfile.Name
data["user"] = tu
data["redirect"] = "/user/profile"
user.Login(r.UserAgent(), r.RemoteAddr)
session, _ := sessionStore.Get(r, "p")
session.Values["user"] = user.Id.Hex()
if tc.Rememberme {
session.Options = &sessions.Options{
Domain: ".mango.dev",
Path: "/",
MaxAge: 86400 * 30 * 12,
}
}
session.Save(r, w)
}
The problem was dundundun I had old cookies stored from before the change that had the Path "/api/1.0/user" and apparently this caused a problem since, I imagine, the longer or deeper path has priority over the shorter, root path, which makes perfect sense in retrospect.

Resources