back to listing index

jackc/pgx

[web search]
Original source (github.com)
Tags: golang go url-shortener
Clipped on: 2016-04-27

Skip to content
Open this file in GitHub Desktop
129 lines (112 sloc) 3.01 KB
1 package main
2
3 import (
4 "github.com/jackc/pgx"
5 log "gopkg.in/inconshreveable/log15.v2"
6 "io/ioutil"
7 "net/http"
8 "os"
9 )
10
11 var pool *pgx.ConnPool
12
13 // afterConnect creates the prepared statements that this application uses
14 func afterConnect(conn *pgx.Conn) (err error) {
15 _, err = conn.Prepare("getUrl", `
16 select url from shortened_urls where id=$1
17 `)
18 if err != nil {
19 return
20 }
21
22 _, err = conn.Prepare("deleteUrl", `
23 delete from shortened_urls where id=$1
24 `)
25 if err != nil {
26 return
27 }
28
29 // There technically is a small race condition in doing an upsert with a CTE
30 // where one of two simultaneous requests to the shortened URL would fail
31 // with a unique index violation. As the point of this demo is pgx usage and
32 // not how to perfectly upsert in PostgreSQL it is deemed acceptable.
33 _, err = conn.Prepare("putUrl", `
34 with upsert as (
35 update shortened_urls
36 set url=$2
37 where id=$1
38 returning *
39 )
40 insert into shortened_urls(id, url)
41 select $1, $2 where not exists(select 1 from upsert)
42 `)
43 return
44 }
45
46 func getUrlHandler(w http.ResponseWriter, req *http.Request) {
47 var url string
48 err := pool.QueryRow("getUrl", req.URL.Path).Scan(&url)
49 switch err {
50 case nil:
51 http.Redirect(w, req, url, http.StatusSeeOther)
52 case pgx.ErrNoRows:
53 http.NotFound(w, req)
54 default:
55 http.Error(w, "Internal server error", http.StatusInternalServerError)
56 }
57 }
58
59 func putUrlHandler(w http.ResponseWriter, req *http.Request) {
60 id := req.URL.Path
61 var url string
62 if body, err := ioutil.ReadAll(req.Body); err == nil {
63 url = string(body)
64 } else {
65 http.Error(w, "Internal server error", http.StatusInternalServerError)
66 return
67 }
68
69 if _, err := pool.Exec("putUrl", id, url); err == nil {
70 w.WriteHeader(http.StatusOK)
71 } else {
72 http.Error(w, "Internal server error", http.StatusInternalServerError)
73 }
74 }
75
76 func deleteUrlHandler(w http.ResponseWriter, req *http.Request) {
77 if _, err := pool.Exec("deleteUrl", req.URL.Path); err == nil {
78 w.WriteHeader(http.StatusOK)
79 } else {
80 http.Error(w, "Internal server error", http.StatusInternalServerError)
81 }
82 }
83
84 func urlHandler(w http.ResponseWriter, req *http.Request) {
85 switch req.Method {
86 case "GET":
87 getUrlHandler(w, req)
88
89 case "PUT":
90 putUrlHandler(w, req)
91
92 case "DELETE":
93 deleteUrlHandler(w, req)
94
95 default:
96 w.Header().Add("Allow", "GET, PUT, DELETE")
97 w.WriteHeader(http.StatusMethodNotAllowed)
98 }
99 }
100
101 func main() {
102 var err error
103 connPoolConfig := pgx.ConnPoolConfig{
104 ConnConfig: pgx.ConnConfig{
105 Host: "127.0.0.1",
106 User: "jack",
107 Password: "jack",
108 Database: "url_shortener",
109 Logger: log.New("module", "pgx"),
110 },
111 MaxConnections: 5,
112 AfterConnect: afterConnect,
113 }
114 pool, err = pgx.NewConnPool(connPoolConfig)
115 if err != nil {
116 log.Crit("Unable to create connection pool", "error", err)
117 os.Exit(1)
118 }
119
120 http.HandleFunc("/", urlHandler)
121
122 log.Info("Starting URL shortener on localhost:8080")
123 err = http.ListenAndServe("localhost:8080", nil)
124 if err != nil {
125 log.Crit("Unable to start web server", "error", err)
126 os.Exit(1)
127 }
128 }