Search code examples
mysqldatabasegogoogle-cloud-sql

Connecting to Cloud SQL (MySQL) through Golang on a live site


I currently have a splash page up and running through Google's App Engine. As of now it is just a page that asks users for their email and name, in order to build an email list. I wanted to store the information on cloud sql, but I cannot seem to get it to work on the live site. As of now I am using MySQLWorkbench to successfully connect to the database on cloud sql and I can input data and submit it which successfully fills in the information to the database on the cloud. I do not receive any connection errors and it works as intended locally on localhost:8080/. I then deployed my changes to app engine and navigate to the site, fill in the information, and hit submit the page hangs and eventually returns a 500 error on my Insert method. This is my first time working with DB on a live site so I cannot understand what the issue is moving from localhost to the live site.

main.go:


import (
    "database/sql"
    "fmt"
    "html/template"
    "log"
    "net/http"
    "os"
    "path/filepath"
    "strings"

    _ "github.com/go-sql-driver/mysql"
)

// Page type
type Page struct {
    Title string
}

type emailListUser struct {
    ID    int
    Name  string
    email string
}

// Connect to DB
func dbConn() (db *sql.DB) {
    dbDriver := "mysql"
    dbUser := "jmcarthur"
    dbPass := "my_password"
    dbName := "tcp(ipFromGoogle:port)/tableName" // Also tried ipFromGoogle:port and ipFromGoogle

    db, err := sql.Open(dbDriver, dbUser+":"+dbPass+"@"+dbName)
    if err != nil {
        log.Println("Failed connection")
    }

    return db
}

// Handlers
func emailListHandler(w http.ResponseWriter, r *http.Request) {
    tpl.ExecuteTemplate(w, "emailList", &Page{Title: "Welcome to My Site"})
}

func main() {
    http.HandleFunc("/", emailListHandler)
    http.HandleFunc("/insert", Insert)
    fs := http.FileServer(http.Dir("static"))
    http.Handle("/css/", fs)
    http.Handle("/fonts/", fs)
    http.Handle("/img/", fs)
    http.Handle("/templates/", fs)

    fmt.Println(http.ListenAndServe(":8080", nil))
}

var tpl = func() *template.Template {
    t := template.New("")
    err := filepath.Walk("./", func(path string, info os.FileInfo, err error) error {
        if strings.Contains(path, ".html") {
            fmt.Println(path)
            _, err = t.ParseFiles(path)
            if err != nil {
                fmt.Println(err)
            }
        }
        return err
    })

    if err != nil {
        panic(err)
    }
    return t
}()

// Insert function
func Insert(w http.ResponseWriter, r *http.Request) {
    db := dbConn()

    if r.Method == "POST" {
        email := r.FormValue("email")
        name := r.FormValue("name")

        insForm, err := db.Prepare("INSERT INTO emailList(email_address, name) VALUES(?,?)")
        if err != nil {
            panic(err.Error())
        }

        insForm.Exec(email, name)
        log.Println("INSERT: Email: " + email + " | Name: " + name)
    }

    defer db.Close()
    http.Redirect(w, r, "/", 301)
}

/* CREATE TABLE tableName(
    email_id INT NOT NULL AUTO_INCREMENT,
    email_address VARCHAR(320) NOT NULL,
    name VARCHAR(150) NOT NULL,
    PRIMARY KEY (email_id)
); */

emailList.html

{{define "emailList"}}
<!DOCTYPE html>
<html lang="en">
    {{template "header" .}}
    <body id="emailListBody" class="text-center">
        <div id="emailListContainer" class="col-12 text-center">
            <img src="./img/picture.png" height="250px" width="250px">
            <h1 class="text-center" id="slogan">slogan</h1>
            <div id="formDiv" class="text-center">
                <form method="POST" action="insert">
                    <label for="email" class="col-12" id="emailLabel">Email:</label>
                    <input type="email" required placeholder="[email protected]" id="email" name="email">
                    <label for="name" class="col-12" id="nameLabel">Name:</label>
                    <input type="text" required placeholder="John Doe" id="name" name="name">
                    <div class="col">
                        <button class="btn btn-default" id="submitButton">SUBMIT</button>
                    </div>
                </form>
            </div>
        </div>
    {{template "footer" .}}
{{end}}

Tree:

.
├── app.yaml
├── cloud_sql_proxy
├── main.go
└── static
    ├── css
    │   └── main.css
    ├── emailList.html
    ├── fonts
    │   ├── EuroStyle\ Normal.ttf
    │   ├── EurostileBold.ttf
    │   └── ethnocentric\ rg.ttf
    ├── img
    │   ├── picture.png
    ├── js
    └── templates
        ├── footer.html
        └── header.html

As I said I can successfully get it working locally, but cannot for the life of me get it to work on the live site. I have ensured that my IP is whitelisted, I tried connecting with the IP in many different ways with and without the port (as can be seen in main.go), but every time I get POST mysite [HTTP/2 500 Internal Server Error 1857ms]. I believe my confusion is with cloud sql and allowing any IP to send data to the database, but I could be completely wrong. Please help, thank you!!


Solution

  • The issue was with my connection function. The solution that ended up working is shown below.

    Note: projectID = name of your google cloud project
          cloudSQLInstace = name of the instance you created for cloud SQL
          my_username and my_password come from created user under SQL -> Users
    
    func dbConn() (db *sql.DB) {
        dbDriver := "mysql"
        dbUser := "my_username"
        dbPass := "my_password"
        dbInstance := "projectID:us-central1:cloudSQLInstace"
        dbName := "emailList"
    
        db, err := sql.Open(dbDriver, dbUser+":"+dbPass+"@unix(/cloudsql/"+dbInstance+")/"+dbName)
        if err != nil {
            log.Fatalf("Failed connection")
        }
    
        return db
    }