diff --git a/examples/url_shortener/README.md b/examples/url_shortener/README.md index cc04d600..beb1802b 100644 --- a/examples/url_shortener/README.md +++ b/examples/url_shortener/README.md @@ -6,20 +6,28 @@ This is a sample REST URL shortener service implemented using pgx as the connect Create a PostgreSQL database and run structure.sql into it to create the necessary data schema. -Edit connectionOptions in main.go with the location and credentials for your database. +Configure the database connection with `DATABASE_URL` or standard PostgreSQL (`PG*`) environment variables or Run main.go: - go run main.go +``` +go run main.go +``` ## Create or Update a Shortened URL - curl -X PUT -d 'http://www.google.com' http://localhost:8080/google +``` +curl -X PUT -d 'http://www.google.com' http://localhost:8080/google +``` ## Get a Shortened URL - curl http://localhost:8080/google +``` +curl http://localhost:8080/google +``` ## Delete a Shortened URL - curl -X DELETE http://localhost:8080/google +``` +curl -X DELETE http://localhost:8080/google +``` diff --git a/examples/url_shortener/main.go b/examples/url_shortener/main.go index af78c4f5..b126fb9f 100644 --- a/examples/url_shortener/main.go +++ b/examples/url_shortener/main.go @@ -1,127 +1,121 @@ -// TODO - Fix after v4 churn ends package main -func main() { +import ( + "context" + "io/ioutil" + "net/http" + "os" + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/log/log15adapter" + "github.com/jackc/pgx/v4/pool" + log "gopkg.in/inconshreveable/log15.v2" +) + +var db *pool.Pool + +// afterConnect creates the prepared statements that this application uses +func afterConnect(ctx context.Context, conn *pgx.Conn) (err error) { + _, err = conn.Prepare(ctx, "getUrl", ` + select url from shortened_urls where id=$1 + `) + if err != nil { + return + } + + _, err = conn.Prepare(ctx, "deleteUrl", ` + delete from shortened_urls where id=$1 + `) + if err != nil { + return + } + + _, err = conn.Prepare(ctx, "putUrl", ` + insert into shortened_urls(id, url) values ($1, $2) + on conflict (id) do update set url=excluded.url + `) + return } -// import ( -// "io/ioutil" -// "net/http" -// "os" +func getUrlHandler(w http.ResponseWriter, req *http.Request) { + var url string + err := db.QueryRow(context.Background(), "getUrl", req.URL.Path).Scan(&url) + switch err { + case nil: + http.Redirect(w, req, url, http.StatusSeeOther) + case pgx.ErrNoRows: + http.NotFound(w, req) + default: + http.Error(w, "Internal server error", http.StatusInternalServerError) + } +} -// "github.com/jackc/pgx/v4" -// "github.com/jackc/pgx/v4/log/log15adapter" -// log "gopkg.in/inconshreveable/log15.v2" -// ) +func putUrlHandler(w http.ResponseWriter, req *http.Request) { + id := req.URL.Path + var url string + if body, err := ioutil.ReadAll(req.Body); err == nil { + url = string(body) + } else { + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } -// var pool *pgx.ConnPool + if _, err := db.Exec(context.Background(), "putUrl", id, url); err == nil { + w.WriteHeader(http.StatusOK) + } else { + http.Error(w, "Internal server error", http.StatusInternalServerError) + } +} -// // afterConnect creates the prepared statements that this application uses -// func afterConnect(conn *pgx.Conn) (err error) { -// _, err = conn.Prepare("getUrl", ` -// select url from shortened_urls where id=$1 -// `) -// if err != nil { -// return -// } +func deleteUrlHandler(w http.ResponseWriter, req *http.Request) { + if _, err := db.Exec(context.Background(), "deleteUrl", req.URL.Path); err == nil { + w.WriteHeader(http.StatusOK) + } else { + http.Error(w, "Internal server error", http.StatusInternalServerError) + } +} -// _, err = conn.Prepare("deleteUrl", ` -// delete from shortened_urls where id=$1 -// `) -// if err != nil { -// return -// } +func urlHandler(w http.ResponseWriter, req *http.Request) { + switch req.Method { + case "GET": + getUrlHandler(w, req) -// _, err = conn.Prepare("putUrl", ` -// insert into shortened_urls(id, url) values ($1, $2) -// on conflict (id) do update set url=excluded.url -// `) -// return -// } + case "PUT": + putUrlHandler(w, req) -// func getUrlHandler(w http.ResponseWriter, req *http.Request) { -// var url string -// err := pool.QueryRow("getUrl", req.URL.Path).Scan(&url) -// switch err { -// case nil: -// http.Redirect(w, req, url, http.StatusSeeOther) -// case pgx.ErrNoRows: -// http.NotFound(w, req) -// default: -// http.Error(w, "Internal server error", http.StatusInternalServerError) -// } -// } + case "DELETE": + deleteUrlHandler(w, req) -// func putUrlHandler(w http.ResponseWriter, req *http.Request) { -// id := req.URL.Path -// var url string -// if body, err := ioutil.ReadAll(req.Body); err == nil { -// url = string(body) -// } else { -// http.Error(w, "Internal server error", http.StatusInternalServerError) -// return -// } + default: + w.Header().Add("Allow", "GET, PUT, DELETE") + w.WriteHeader(http.StatusMethodNotAllowed) + } +} -// if _, err := pool.Exec("putUrl", id, url); err == nil { -// w.WriteHeader(http.StatusOK) -// } else { -// http.Error(w, "Internal server error", http.StatusInternalServerError) -// } -// } +func main() { + logger := log15adapter.NewLogger(log.New("module", "pgx")) -// func deleteUrlHandler(w http.ResponseWriter, req *http.Request) { -// if _, err := pool.Exec("deleteUrl", req.URL.Path); err == nil { -// w.WriteHeader(http.StatusOK) -// } else { -// http.Error(w, "Internal server error", http.StatusInternalServerError) -// } -// } + poolConfig, err := pool.ParseConfig(os.Getenv("DATABASE_URL")) + if err != nil { + log.Crit("Unable to parse DATABASE_URL", "error", err) + os.Exit(1) + } -// func urlHandler(w http.ResponseWriter, req *http.Request) { -// switch req.Method { -// case "GET": -// getUrlHandler(w, req) + poolConfig.AfterConnect = afterConnect + poolConfig.ConnConfig.Logger = logger -// case "PUT": -// putUrlHandler(w, req) + db, err = pool.ConnectConfig(context.Background(), poolConfig) + if err != nil { + log.Crit("Unable to create connection pool", "error", err) + os.Exit(1) + } -// case "DELETE": -// deleteUrlHandler(w, req) + http.HandleFunc("/", urlHandler) -// default: -// w.Header().Add("Allow", "GET, PUT, DELETE") -// w.WriteHeader(http.StatusMethodNotAllowed) -// } -// } - -// func main() { -// logger := log15adapter.NewLogger(log.New("module", "pgx")) - -// var err error -// connPoolConfig := pgx.ConnPoolConfig{ -// ConnConfig: pgx.ConnConfig{ -// Host: "127.0.0.1", -// User: "jack", -// Password: "jack", -// Database: "url_shortener", -// Logger: logger, -// }, -// MaxConnections: 5, -// AfterConnect: afterConnect, -// } -// pool, err = pgx.NewConnPool(connPoolConfig) -// if err != nil { -// log.Crit("Unable to create connection pool", "error", err) -// os.Exit(1) -// } - -// http.HandleFunc("/", urlHandler) - -// log.Info("Starting URL shortener on localhost:8080") -// err = http.ListenAndServe("localhost:8080", nil) -// if err != nil { -// log.Crit("Unable to start web server", "error", err) -// os.Exit(1) -// } -// } + log.Info("Starting URL shortener on localhost:8080") + err = http.ListenAndServe("localhost:8080", nil) + if err != nil { + log.Crit("Unable to start web server", "error", err) + os.Exit(1) + } +} diff --git a/go.mod b/go.mod index 033beea3..ae9acbed 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,14 @@ go 1.12 require ( github.com/cockroachdb/apd v1.1.0 + github.com/go-stack/stack v1.8.0 // indirect github.com/jackc/pgconn v0.0.0-20190424214952-1e3961bd0ea4 github.com/jackc/pgio v1.0.0 - github.com/jackc/pgproto3 v1.1.0 github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0 github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b + github.com/mattn/go-colorable v0.1.1 // indirect + github.com/mattn/go-isatty v0.0.7 // indirect github.com/rs/zerolog v1.13.0 github.com/satori/go.uuid v1.2.0 github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 @@ -18,4 +20,5 @@ require ( go.uber.org/atomic v1.4.0 // indirect go.uber.org/zap v1.9.1 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373 + gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec ) diff --git a/go.sum b/go.sum index 1796077e..c6f94572 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3 h1:ZFYpB74Kq8xE9gmfxCmXD6QxZ27ja+j3HwGFc+YurhQ= @@ -34,6 +36,11 @@ github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0 h1:/5u4a+KGJptBRqGzPvYQL9p0d/tPR4S31+Tnzj9lEO4= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -63,6 +70,7 @@ golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a h1:Igim7XhdOpBnWPuYJ70XcN golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= @@ -71,3 +79,5 @@ golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373 h1:PPwnA7z1Pjf7XYaBP9GL1 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec h1:RlWgLqCMMIYYEVcAR5MDsuHlVkaIPDAF+5Dehzg8L5A= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=