Contact form mail handler example for go on appengine - google-app-engine

To my surprise I did not find a contact form mail handler example for go? I don't feel like making a wheel today, are there examples available?
EDIT: (cut and paste answer)
package bin
import (
"fmt"
"net/http"
netMail "net/mail"
"appengine"
"appengine/mail"
)
func contact(w http.ResponseWriter, r *http.Request) {
c := appengine.NewContext(r)
name := r.FormValue("name")
email := r.FormValue("email")
subject := r.FormValue("subject")
message := r.FormValue("message")
msg := &mail.Message{
Sender: name + " <...#yourappid.appspotmail.com>",
To: []string{"...#..."},
ReplyTo: email,
Subject: subject,
Body: message,
Headers: netMail.Header{
"On-Behalf-Of": []string{email},
},
}
if err := mail.Send(c, msg); err != nil {
c.Errorf("Couldn't send email: %v", err)
fmt.Fprint(w, "Mail NOT send! Error")
}else{
fmt.Fprint(w, "Mail send.")
}
}
NOTE:
1) ReplyTo only works in gmail if Sender and To are different.
2) Sender should have admin role in google cloud console or something#yourappid.appspotmail.com.

This is most likely failing because the Sender can only be
an email of a developer of you app, or
something#yourappid.appspotmail.com
I suggest that you hard-code the sender's email, and use the On-Behalf-Of header in which you include the original sender's name/email.
Also, WriteString accepts a single string, not a []string slice.
The minimum modifications for your example would be:
…
msg := &mail.Message{
Sender: name + " <developer#yourapp.com>",
To: []string{"...#gmail.com"},
Subject: subject,
Body: message,
Headers: netMail.Header{
"On-Behalf-Of": []string{email},
},
}
…
Also, you'll need to make sure the user's name doesn't actually contain an email address. That could cause you problems…
The best would be to do as #elithrar suggested and validate your form.

Related

Quick and Easy Post to Slack Webhook from Golang AppEngine

Not so much a question as help for others having this problem. Took a fair amount of beating my head against a wall to make this work. (as much I as love golang, you do have think a little differently) - This will also work as a generic way to do any sort of post to an outside source in AppEngine.
Here is the function I am using to post simple messages to a slack channel via webhook. (assumes you know how to set up a webhook in slack - very easy to do - https://get.slack.help/hc/en-us/articles/115005265063-Incoming-WebHooks-for-Slack ) - NOTE: while there are a fair # of additional parameters you can pass in the json message (see link above) simple things like email addresses and image urls / web addresses will be automatically parsed by slack if passed in the 'text' parameter.
import (
"bytes"
"google.golang.org/appengine"
"google.golang.org/appengine/urlfetch"
"net/http"
)
func postSlackBetaSignup(req *http.Request, msg string) string {
ctx := appengine.NewContext(req);
request := urlfetch.Client(ctx);
data := []byte("{'text': '" + msg + "'}");
body := bytes.NewReader(data);
resp, err := request.Post("https://hooks.slack.com/services/<<<YOUR WEBHOOK HERE>>>", "application/json", body);
if err != nil {
return err.Error();
} else {
return resp.Status;
}
}
import (
"bytes"
"google.golang.org/appengine"
"google.golang.org/appengine/urlfetch"
"net/http"
)
func postSlackBetaSignup(req *http.Request, msg string) string {
ctx := appengine.NewContext(req);
request := urlfetch.Client(ctx);
data := []byte("{'text': '" + msg + "'}");
body := bytes.NewReader(data);
resp, err := request.Post("https://hooks.slack.com/services/<<<YOUR WEBHOOK HERE>>>", "application/json", body);
if err != nil {
return err.Error();
} else {
return resp.Status;
}
}

Mongo-Go-Driver Failing to Connect

So I am trying to use https://github.com/mongodb/mongo-go-driver to connect to a mongo database in golang.
Here is my connection handler:
var DB *mongo.Database
func CreateConnectionHandler()(*mongo.Database, error){
fmt.Println("inside createConnection in database package")
godotenv.Load()
fmt.Println("in CreateConnectionHandler and SERVER_CONFIG: ")
fmt.Println(os.Getenv("SERVER_CONFIG"))
uri:=""
if os.Getenv("SERVER_CONFIG")=="kubernetes"{
fmt.Println("inside kubernetes db config")
uri = "mongodb://patientplatypus:SUPERSECRETPASSDOOT#
mongo-release-mongodb.default.svc.cluster.local:27017/
platypusNEST?authMechanism=SCRAM-SHA-1"
}else if os.Getenv("SERVER_CONFIG")=="compose"{
fmt.Println("inside compose db config")
uri = "mongodb://datastore:27017"
}
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
client, err := mongo.Connect(ctx, uri)
if err != nil {
return nil, fmt.Errorf("mongo client couldn't connect: %v", err)
}
DB := client.Database("platypusNEST")
return DB, nil
}
And the error I am getting:
api | database/connection.go:29:30: cannot use uri (type
string) as type *options.ClientOptions in argument to mongo.Connect
So I tried replacing uri with the connection string like this:
client, err := mongo.Connect(ctx, "mongodb://datastore:27017")
But I still got the error.
Compare this with what is in the documentation:
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
client, err := mongo.Connect(ctx, "mongodb://localhost:27017")
And it is exactly the same! I'm really not sure why there is this error. Any ideas?
To those who come searching - the docs are out of date as of this posting, but their latest push here: https://github.com/mongodb/mongo-go-driver/commit/32946b1f8b9412a6a94e68ff789575327bb257cf has them doing this with the connect:
client, err := mongo.NewClient(options.Client().ApplyURI(uri))
You will also now need to import the options package. Happy hacking.
EDIT: thanks vcanales for finding this - you're a gentleman and a scholar.
In addition to the accepted answer - this snippet below may be improved by using an environment variable to pass in the Mongodb URL.
package main
import (
"context" //use import withs " char
"fmt"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
)
func ConnectMongo() {
var (
client *mongo.Client
mongoURL = "mongodb://localhost:27017"
)
// Initialize a new mongo client with options
client, err := mongo.NewClient(options.Client().ApplyURI(mongoURL))
// Connect the mongo client to the MongoDB server
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
err = client.Connect(ctx)
// Ping MongoDB
ctx, _ = context.WithTimeout(context.Background(), 10*time.Second)
if err = client.Ping(ctx, readpref.Primary()); err != nil {
fmt.Println("could not ping to mongo db service: %v\n", err)
return
}
fmt.Println("connected to nosql database:", mongoURL)
}
func main() {
ConnectMongo()
}
More information on options and readpref respectively:
https://docs.mongodb.com/manual/reference/method/cursor.readPref/index.html
https://docs.mongodb.com/manual/core/read-preference/

Is the Go Appengine tutorial missing LoginURL redirect statements?

I'm not getting redirected to the Google account sign-in screen when I run the demo code.
https://cloud.google.com/appengine/docs/standard/go/getting-started/authenticating-users
The tutorial mentions: "Note that if the user is not signed in, an HTTP status code of 302 Found redirects the browser to the Google account sign-in screen."
func sign(w http.ResponseWriter, r *http.Request) {
// [START new_context]
c := appengine.NewContext(r)
// [END new_context]
g := Greeting{
Content: r.FormValue("content"),
Date: time.Now(),
}
// [START if_user]
if u := user.Current(c); u != nil {
g.Author = u.String()
}
key := datastore.NewIncompleteKey(c, "Greeting", guestbookKey(c))
_, err := datastore.Put(c, key, &g)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/", http.StatusFound)
// [END if_user]
}
Source code:
https://github.com/GoogleCloudPlatform/appengine-guestbook-go/blob/part4-usingdatastore/hello.go
It seems something is missing e.g.:
if u == nil {
url, _ := user.LoginURL(ctx, "/")
fmt.Fprintf(w, `Sign in or register`, url)
return
}
If I'm wrong, how does the current code redirect user to the sign-in screen?
You are correct. To log in a user, you'll have to redirect them to a LoginURL as above. Once a user accesses this URL, app engine will let them log in using their google account.
However, the example code you've linked above does not require a user to be logged in. If a user is logged in, the code gets their ID and uses that as the greeting's author. If not, it just calls the user anonymous, as per the template:
{{with .Author}}
<p><b>{{.}}</b> wrote:</p>
{{else}
<p>An anonymous person wrote:</p>
{{end}}

Cannot change consistency using "goapp test"

Google's AppEngine service provides an eventually consistent database for storage of application data in production. For testing, Google provides a similar database that emulates the consistency characteristics of the production database. Testing requirements may vary so Google supplies command line parameters to their test server, dev_appserver.py, that modify the consistency characteristics as needed for testing.
I am using the goapp tools to run our automated test environment for my AppEngine development. goapp test is responsible for running our automated server API tests. goapp test does not appear to have a way of setting the datastore's consistency level via the command line parameters, unlike dev_appserver.py, even though goapp test launches dev_appserver.py at some point during the testing process.
Is there a way to pass command line parameters to dev_appserver.py from goapp test? If not, is there an alternative method for setting the consistency from the command line?
I presume you are using the aetest package.
If this is the case you should set the StronglyConsistentDatastore member of the aetest.Options struct accordingly.
Here is an example:
hello.go
package hello
import (
"fmt"
"net/http"
"time"
"appengine"
datastore "appengine/datastore"
)
type Employee struct {
FirstName string
LastName string
HireDate time.Time
}
func init() {
http.HandleFunc("/", handler)
}
func handler(w http.ResponseWriter, r *http.Request) {
ctx := appengine.NewContext(r)
q := datastore.NewQuery("Employee").Filter("FirstName =", "Antonio")
var people []Employee
if _, err := q.GetAll(ctx, &people); err != nil {
fmt.Fprintf(w, "Error %v", err)
}
fmt.Fprintf(w, "Matches %v", len(people))
}
hello_test.go
package hello
import (
"time"
"testing"
"net/http/httptest"
"appengine"
"appengine/aetest"
datastore "appengine/datastore"
)
const consistency_strong = true; // set to false and the test will fail
func TestMyHandler(t *testing.T) {
options := &aetest.Options{StronglyConsistentDatastore: consistency_strong}
inst, err := aetest.NewInstance(options)
if err != nil {
t.Fatalf("Failed to create instance: %v", err)
}
defer inst.Close()
req, err := inst.NewRequest("GET", "/", nil)
if err != nil {
t.Fatalf("Failed to create req1: %v", err)
}
ctx := appengine.NewContext(req)
employee := &Employee{
FirstName: "Antonio",
LastName: "Salieri",
HireDate: time.Now(),
}
key := datastore.NewIncompleteKey(ctx, "Employee", nil)
_, err = datastore.Put(ctx, key, employee)
if err != nil {
t.Fatalf("Error setting test data: %v", err)
}
w := httptest.NewRecorder()
handler(w, req)
if w.Body.String() != "Matches 1" {
t.Fatalf("Expected 1 record got %v", w.Body)
}
}
As you have mentioned the go tools merely wrap dev_appserver.py. This means that if you are not using aetest you can always run your app with a chosen consistency policy like so:
/usr/local/go_appengine/dev_appserver.py --datastore_consistency_policy consistent .

Fetching asna redirect url parameter on google app engine in golang

My asana oauth redirect URL is something like
https://testapp.appspot.com/api/redirect.asana#access_token=123456789
I am not sure how to fetch access_token now.
Note : if I am changing ? to # then its working fine by using r.FormValue("access_token").
The reason why r.FormValue() does not get it is because URL parameters are separated by ? but in your URL it is not.
The # is used to separate a fragment for references, so your access_token should be in r.URL.Fragment... but it won't.
You can't test it from the browser
Fragments are not sent over to the server, fragments are for the browsers. There was an issue covering this:
net/http: document fields set in Request.URL in handler #3805
It is also included in the doc of http.Request:
For server requests the URL is parsed from the URI supplied on the Request-Line as stored in RequestURI. For most requests, fields other than Path and RawQuery will be empty. (See RFC 2616, Section 5.1.2)
Code to get it from the request
If a non-browser client does send it as part of the request path, you can use simple string operations to get the token value: it is the part after the = character. You can use strings.Index() to find the "=":
raw := r.URL.Path
if idx := strings.Index(raw, "="); idx >= 0 && idx < len(raw)-1 {
token := raw[idx+1:]
fmt.Fprintln(w, "Token:", token)
}
Output:
Token: 123456789
As an alternative solution you can also use strings.Split() to split it by "=", 2nd element will be the value of the token:
parts := strings.Split(r.URL.Path, "=")
fmt.Fprintln(w, "Parts:", parts)
if len(parts) == 2 {
fmt.Fprintln(w, "Token:", parts[1])
}
Output:
[/api/redirect.asana#access_token 123456789]
Token: 123456789
Code to test it
Here is a code using net/http to call your server that will send a path being "/api/redirect.asana#access_token=123456789", and it will print the response body to the standard output (console):
c := &http.Client{}
req, err := http.NewRequest("GET", "http://localhost:8080/", nil)
if err != nil {
panic(err)
}
req.URL.Path = "/api/redirect.asana#access_token=123456789"
resp, err := c.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
io.Copy(os.Stdout, resp.Body)

Resources