Seperate database connection function from the query function - mysql

I have been trying to execute queries with http://www.github.com/go-sql-driver/mysql and have been following this tutorial: https://tutorialedge.net/golang/golang-mysql-tutorial/
Now this tutorial does everything in 1 custom function, but I've tried to split up opening the database connection and the queries itself in seperate functions. The way I've done this is like this:
package main
import (
"fmt"
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
type SqlResult struct {
id int
}
var db *sql.DB
func main() {
dbConnection:= openDatabaseConnection();
getID(dbConnection);
}
func openDatabaseConnection() *sql.DB {
db,err:= sql.Open("mysql","username:password#tcp(127.0.0.1:3306)/test");
if err != nil {
panic(err.Error())
}
defer db.Close()
return db
}
func getID(db *sql.Db) {
results,err:= db.Query("SELECT id FROM test")
for results.Next() {
var result SqlResult
err=results.Scan(&result.id)
if err != nil {
panic(err.Error())
}
fmt.Print(result.id)
}
}
This doesn't print out anything but also doesn't give an error.
Now when I put my code in 1 function, like this:
package main
import (
"fmt"
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
type SqlResult struct {
id int
}
var db *sql.DB
func main() {
openDatabaseConnection();
}
func openDatabaseConnection() *sql.DB {
db,err:= sql.Open("mysql","username:password#tcp(127.0.0.1:3306)/test");
if err != nil {
panic(err.Error())
}
results,err:= db.Query("SELECT id FROM test")
for results.Next() {
var result SqlResult
err=results.Scan(&result.id)
if err != nil {
panic(err.Error())
}
fmt.Print(result.id)
}
defer db.Close()
return db
}
This returns me all the id's, but I want to split the functionality up. It doesn't seem like opening the database connection everytime I need a new query is the right solution.
Does anyone know what I'm doing wrong in my first example and if so, what would be the right way of the way I want it to be?

"what would be the right way of the way I want it to be?"
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB
func main() {
db = openDatabaseConnection()
defer db.Close()
printIds(db)
}
func openDatabaseConnection() *sql.DB {
db, err := sql.Open("mysql", "username:password#tcp(127.0.0.1:3306)/test")
if err != nil {
panic(err)
} else if err := db.Ping(); err != nil {
panic(err)
}
return db
}
func printIds(db *sql.DB) {
rows, err := db.Query("SELECT id FROM test")
if err != nil {
panic(err)
}
defer rows.Close()
for rows.Next() {
var id int
if err := rows.Scan(&id); err != nil {
panic(err)
}
fmt.Println(id)
}
if err := rows.Err(); err != nil {
panic(err)
}
}

Related

How to set up web server to perform POST Request in Go?

I want to set up a web server to perform a POST request. How does the post request get executed with the code below since only HandleFunc and ListenAndServe are defined in main function?
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
)
func post(w http.ResponseWriter, r *http.Request) {
const myurl string = "http://localhost:8000/"
request := strings.NewReader(`
{
"Name":"Tom",
"Age":"20"
}
`)
response, err := http.Post(myurl, "application/json", request)
content, err := ioutil.ReadAll(response.Body)
if err != nil {
panic(err)
}
fmt.Println(string(content))
defer response.Body.Close()
}
func main() {
http.HandleFunc("/", post)
log.Fatal(http.ListenAndServe(":8000", nil))
}
Here is a basic example of how you could go about it. I am using the same program to run both, the server and the client. This is just for demonstration purposes. You can of course make them separate programs.
// use struct to represent the data
// to recieve and send
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
// run the example
func main() {
// start the server in a goroutine
go server()
// wait 1 second to give the server time to start
time.Sleep(time.Second)
// make a post request
if err := client(); err != nil {
fmt.Println(err)
}
}
// basic web server to receive a request and
// decode the body into a user struct
func server() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
user := &Person{}
err := json.NewDecoder(r.Body).Decode(user)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
fmt.Println("got user:", user)
w.WriteHeader(http.StatusCreated)
})
if err := http.ListenAndServe(":8080", nil); err != http.ErrServerClosed {
panic(err)
}
}
// a simple client that posts a user to the server
func client() error {
user := &Person{
Name: "John",
Age: 30,
}
b := new(bytes.Buffer)
err := json.NewEncoder(b).Encode(user)
if err != nil {
return err
}
resp, err := http.Post("http://localhost:8080/", "application/json", b)
if err != nil {
return err
}
defer resp.Body.Close()
fmt.Println(resp.Status)
return nil
}
Here is the working example: https://go.dev/play/p/34GT04jy_uA

How can I display the JSON response from the API in Go?

I'm invoking a small API that gives back a JSON response with a Todo object. How can I display that in my Go web server? Right now, all that's displaying is 'Todo', which I hard-coded. How can that be replaced by the API JSON response?
Here is the code:
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
)
// Todo Structure
type Todo struct {
userID int
id int
title string
completed bool
}
func main() {
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "<h1>Todo</h1>")
})
response, err := http.Get("http://jsonplaceholder.typicode.com/todos/1")
if err != nil {
fmt.Print(err.Error())
os.Exit(1)
}
responseData, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(responseData))
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}
You have to write a function delegated to retrieve the data from the external website. Than use the http.HandleFunc in order to expose the method at your endpoint, here an example:
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
)
// Todo Structure
type Todo struct {
userID int
id int
title string
completed bool
}
func main() {
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, callHTTP())
})
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}
func callHTTP() string {
var (
response *http.Response
err error
responseData []byte
)
if response, err = http.Get("http://jsonplaceholder.typicode.com/todos/1"); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
if responseData, err = ioutil.ReadAll(response.Body); err != nil {
log.Fatal(err)
}
ret := string(responseData)
fmt.Println(ret)
return ret
}
Note:
Avoid to use the default http client in production. You can try some HTTP framework like GIN. I prefer fasthttp, here you can view some code as example: https://github.com/alessiosavi/StreamingServer

How to get values from a variable of type *sql.Rows in the view?

In posts table, there are attributes like title, content.
I can get data of type *sql.Rows and pass them to the view by
posts, err := db.Query("SELECT id, title FROM posts WHERE id = 1")
and
err = tpl.ExecuteTemplate(w, "index.gohtml", posts)
but I couldn't display the title value in the view. Here is my code.
index.go
package main
import (
"net/http"
"fmt"
"log"
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
func index(w http.ResponseWriter, r *http.Request) {
db, err := sql.Open("mysql", "root:****#/database")
if err != nil {
panic(err.Error())
}
defer db.Close()
posts, err := db.Query("SELECT id, title FROM posts WHERE id = 1")
var id int
var title string
for posts.Next() {
err = posts.Scan(&id, &title)
if err != nil {
panic(err.Error())
}
}
fmt.Println(posts)
defer posts.Close()
err = tpl.ExecuteTemplate(w, "index.gohtml", posts)
if err != nil {
log.Println(err)
}
}
index.gohtml
<h1>Awesome Posts</h1>
{{$posts := .}}
<p>{{$posts}}</p>
There are a few mistakes in your code and I think you have misunderstood how to extract data using the sql package.
As Flimzy said in the comments, you should pass a proper context struct which contains your ID and Title values somewhere.
If you check the docs for sql.Rows you will see how to extract the data at each of the rows from your query...and in fact you already know how to get the rows and column values - by using the Next() and Scan() methods. But this should not be done by code in the HTML template, it should store the results in some variable which is passed to the template.
Quick Answer
A quick answer to your question would be to change how you pass the values into the template and amend the template. Seeing as you declare id and title variables you should pass them to the template:
err = tpl.ExecuteTemplate(w, "index.gohtml", map[string]interface{}{"ID": id,"Title": title})
if err != nil {
log.Println(err)
}
<h1>Awesome Posts</h1>
<p>{{.ID}} - {{.Title}}</p>
Having a Post model
A better solution would be to have a struct which holds all of the properties of the post, and use this to Scan into.
type Post struct{
ID int
Title string
}
...
var p Post
...
_ = rows.Scan(&p)
However, there is another problem with the way you are storing the results of the query. You are using db.Query to return a single row - This is an assumption as you have WHERE ID=1. If you expect only one post to be returned then use the QueryRow method: (N.B. you can chain on the Scan method for simplicity)
var p Post
// error handling removed for brevity
_ = db.QueryRow("SELECT id, title FROM posts WHERE id = 1").Scan(&p)
_ = tpl.ExecuteTemplate(w, "index.gohtml", p)
If, however, you are expecting to retrieve multiple posts (and you were just adding the where clause for simplicity), then you need to Scan into a Post struct, and append into a slice of Posts.
rows, _ := db.Query("SELECT id, title FROM posts")
defer rows.Close()
var posts []Post
for rows.Next() {
var p Post
_ = posts.Scan(&id, &p) // you should handle error here.
posts = append(posts, p)
}
if err = tpl.ExecuteTemplate(w, "index.gohtml", posts); err!=nil{
log.Println(err)
}
Other considerations
You should not be creating a connection to the DB within the HTTP handler. One way is to have a global variable which holds the connection. A struct with embedded connection can work and/or it is possible to abstract away the connection into a package as well.
/db/db.go
package db
import (
"database/sql"
// MYSQL driver
_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB
// Open handles the opening of the DB
func Open(connstr string) (err error) {
db, err = sql.Open("mysql", connstr)
if err != nil {
return err
}
return nil
}
// Close handles the closing of the DB
func Close() error {
return db.Close()
}
/db/posts.go
package db
// Post model
type Post struct {
ID uint
Title string
Body string
}
const (
getPosts = `SELECT id, title, body FROM posts`
getAPost = `SELECT id, title, body FROM posts WHERE id=?`
)
// GetPosts will return all posts from the DB
func GetPosts() ([]Post, error) {
rows, err := db.Query(getPosts)
if err != nil {
return nil, err
}
var posts []Post
for rows.Next() {
var p Post
if err := rows.Scan(&p.ID, &p.Title, &p.Body); err != nil {
return nil, err
}
posts = append(posts, p)
}
return posts, nil
}
// GetPost will return single post identified by ID from the DB
func GetPost(id uint) (Post, error) {
var p Post
if err := db.QueryRow(getAPost, id).Scan(&p.ID, &p.Title, &p.Body); err != nil {
return p, err
}
return p, nil
}
main.go
import (
"chilledoj/sopost/db" // this is the absolute path to the db folder
"html/template"
"log"
"net/http"
"strconv"
"flag"
"github.com/gorilla/mux"
)
var dbconn string
func init() {
flag.StringVar(&dbconn, "dbconn", "", "MYSQL DB Connection string")
flag.Parse()
}
func main() {
if dbconn == "" {
log.Fatal("DB Connection string not set")
}
if err := db.Open(dbconn); err != nil {
log.Fatal(err)
}
defer db.Close()
r := mux.NewRouter()
r.HandleFunc("/", indexHandler())
r.HandleFunc("/posts", postsHandler())
r.HandleFunc("/posts/{id}", postHandler())
if err := http.ListenAndServe(":8080", r); err != nil {
log.Panic(err)
}
}
var indexHandler = func() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte(`<h1>Welcome</h1>Posts`))
}
}
var postsHandler = func() http.HandlerFunc {
tmpl, err := template.New("posts").Parse(`<h1>Awesome Posts</h1>
<ul>{{range .}}
<li>{{.Title}}</li>
{{end}}</ul>
<hr/>
Home`)
if err != nil {
log.Panic(err)
}
return func(w http.ResponseWriter, r *http.Request) {
posts, err := db.GetPosts()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
err = tmpl.Execute(w, posts)
if err != nil {
log.Printf("There was a template Error.\n%v\n", err)
}
}
}
var postHandler = func() http.HandlerFunc {
tmpl, err := template.New("posts").Parse(`<h1>Awesome Posts</h1>
<h2>{{.Title}}</h2>
<p>{{.Body}}</p>
<hr/>
Home
Posts`)
if err != nil {
log.Panic(err)
}
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.ParseInt(vars["id"], 10, 32)
if err != nil {
http.NotFound(w, r)
return
}
post, err := db.GetPost(uint(id))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
tmpl.Execute(w, post)
}
}
Run the above using
go run main.go -dbconn [dbuser]:[dbpass]#/[dbname]?parseTime=true
Another way is to use dependency injection and have a function take in a db connection but return an http.HandlerFunc. e.g.
var indexHandler = function (db *sql.DB) http.HandlerFunc{
return function(w http.ResponseWriter, r *http.Request){
// now have access to db
}
}
http.HandleFunc("/posts", indexHandler())

Storing and retrieving RSA encryption key

I am trying to build an API, but to secure it properly I believe I need to go with RSA encryption for a private key stored on my server and a public key for the client. I have stored the generated private key into a JSON file, I plan to store on my server but to write to JSON, I needed to convert the type too []byte. Now when I try to retrieve the private key to generate a public key, but it will not let me use type bytes for *Publickey
The only other way I can think of to accomplish this goal is to seed the random number generator, so I can have the seed a secret on my server and then my private key should always generate to the same thing, any help this this would be great.
package main
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"encoding/json"
"fmt"
"io/ioutil"
"os"
)
func main() {
mimicPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
buf := new(bytes.Buffer)
json.NewEncoder(buf).Encode(mimicPrivateKey)
secrets, _ := os.OpenFile("secrets.json", os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
// Close the secrets file when the surrounding function exists
secrets.WriteString(buf.String())
secrets.Close()
secrets, _ = os.OpenFile("secrets.json", os.O_RDWR, 0666)
serverKey, _ := ioutil.ReadAll(secrets)
if serverKey != nil {
fmt.Println("can not open key")
}
serverKeyPublic := &serverKey.PublicKey
}
You need to Unmarshal it:
var data *rsa.PrivateKey
err = json.Unmarshal(serverKey, &data)
if err != nil {
panic(err)
}
And you may use
err = ioutil.WriteFile("secrets.json", buf.Bytes(), 0666)
and
serverKey, err := ioutil.ReadFile("secrets.json")
See:
package main
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"encoding/json"
"fmt"
"io/ioutil"
)
func main() {
mimicPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
panic(err)
}
var buf bytes.Buffer
err = json.NewEncoder(&buf).Encode(mimicPrivateKey)
if err != nil {
panic(err)
}
err = ioutil.WriteFile("secrets.json", buf.Bytes(), 0666)
if err != nil {
panic(err)
}
serverKey, err := ioutil.ReadFile("secrets.json")
if err != nil {
panic(err)
}
var data *rsa.PrivateKey
err = json.Unmarshal(serverKey, &data)
if err != nil {
panic(err)
}
serverKeyPublic := data.PublicKey
fmt.Println(serverKeyPublic)
}

How do I connect to mysql server with Go and go-sql-driver?

I am following the tutorials here and here but I am unable to connect to the test database that came with mySQL installation. I can connect to mySql through the command line. What am I missing? When I run the code below I get the error "cannot ping":
package main
import (
"fmt"
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "/test")
if err != nil {
fmt.Println(err)
return
}
defer db.Close()
err = db.Ping()
if err != nil {
fmt.Println("cannot ping")
return
}
}
For example, substitute your MySQL user name and password for the words user and password,
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "user:password#/test")
if err != nil {
fmt.Println(err)
return
}
defer db.Close()
err = db.Ping()
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Ping")
}
Output:
Ping