[ ] Go: |
- Reddit :
Go (HTTP ) , -?
, , . , , x/net/context.
? .
? ? ? , .
.
, .
DRY (Don't Repeat Yourself . ), , .
, . MVC (Model View Controller) HTTP , InitDB(), .
bookstore
+-- main.go
+-- models
+-- books.go
+-- db.go
File: main.go
package main
import (
"bookstore/models"
"fmt"
"net/http"
)
func main() {
models.InitDB("postgres://user:pass@localhost/bookstore")
http.HandleFunc("/books", booksIndex)
http.ListenAndServe(":3000", nil)
}
func booksIndex(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, http.StatusText(405), 405)
return
}
bks, err := models.AllBooks()
if err != nil {
http.Error(w, http.StatusText(500), 500)
return
}
for _, bk := range bks {
fmt.Fprintf(w, "%s, %s, %s, lb%.2f\n", bk.Isbn, bk.Title, bk.Author, bk.Price)
}
}
File: models/db.go
package models
import (
"database/sql"
_ "github.com/lib/pq"
"log"
)
var db *sql.DB
func InitDB(dataSourceName string) {
var err error
db, err = sql.Open("postgres", dataSourceName)
if err != nil {
log.Panic(err)
}
if err = db.Ping(); err != nil {
log.Panic(err)
}
}
File: models/books.go
package models
type Book struct {
Isbn string
Title string
Author string
Price float32
}
func AllBooks() ([]*Book, error) {
rows, err := db.Query("SELECT * FROM books")
if err != nil {
return nil, err
}
defer rows.Close()
bks := make([]*Book, 0)
for rows.Next() {
bk := new(Book)
err := rows.Scan(&bk.Isbn, &bk.Title, &bk.Author, &bk.Price)
if err != nil {
return nil, err
}
bks = append(bks, bk)
}
if err = rows.Err(); err != nil {
return nil, err
}
return bks, nil
}
/books :
$ curl -i localhost:3000/books
HTTP/1.1 200 OK
Content-Length: 205
Content-Type: text/plain; charset=utf-8
978-1503261969, Emma, Jayne Austen, lb9.44
978-1505255607, The Time Machine, H. G. Wells, lb5.99
978-1503379640, The Prince, Niccol`o Machiavelli, lb6.99
, :
. , ?
InitDB , ( ). "yourproject/config" , . , .
. , , HTTP .
( ), , . , .
, , , Env:
type Env struct {
db *sql.DB
logger *log.Logger
templates *template.Template
}
, Env. ( ) .
:
File: main.go
package main
import (
"bookstore/models"
"database/sql"
"fmt"
"log"
"net/http"
)
type Env struct {
db *sql.DB
}
func main() {
db, err := models.NewDB("postgres://user:pass@localhost/bookstore")
if err != nil {
log.Panic(err)
}
env := &Env{db: db}
http.HandleFunc("/books", env.booksIndex)
http.ListenAndServe(":3000", nil)
}
func (env *Env) booksIndex(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, http.StatusText(405), 405)
return
}
bks, err := models.AllBooks(env.db)
if err != nil {
http.Error(w, http.StatusText(500), 500)
return
}
for _, bk := range bks {
fmt.Fprintf(w, "%s, %s, %s, lb%.2f\n", bk.Isbn, bk.Title, bk.Author, bk.Price)
}
}
File: models/db.go
package models
import (
"database/sql"
_ "github.com/lib/pq"
)
func NewDB(dataSourceName string) (*sql.DB, error) {
db, err := sql.Open("postgres", dataSourceName)
if err != nil {
return nil, err
}
if err = db.Ping(); err != nil {
return nil, err
}
return db, nil
}
File: models/books.go
package models
import "database/sql"
type Book struct {
Isbn string
Title string
Author string
Price float32
}
func AllBooks(db *sql.DB) ([]*Book, error) {
rows, err := db.Query("SELECT * FROM books")
if err != nil {
return nil, err
}
defer rows.Close()
bks := make([]*Book, 0)
for rows.Next() {
bk := new(Book)
err := rows.Scan(&bk.Isbn, &bk.Title, &bk.Author, &bk.Price)
if err != nil {
return nil, err
}
bks = append(bks, bk)
}
if err = rows.Err(); err != nil {
return nil, err
}
return bks, nil
}
Env, Env :
File: main.go
package main
import (
"bookstore/models"
"database/sql"
"fmt"
"log"
"net/http"
)
type Env struct {
db *sql.DB
}
func main() {
db, err := models.NewDB("postgres://user:pass@localhost/bookstore")
if err != nil {
log.Panic(err)
}
env := &Env{db: db}
http.Handle("/books", booksIndex(env))
http.ListenAndServe(":3000", nil)
}
func booksIndex(env *Env) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, http.StatusText(405), 405)
return
}
bks, err := models.AllBooks(env.db)
if err != nil {
http.Error(w, http.StatusText(500), 500)
return
}
for _, bk := range bks {
fmt.Fprintf(w, "%s, %s, %s, lb%.2f\n", bk.Isbn, bk.Title, bk.Author, bk.Price)
}
})
}
, :
. , ( sql.DB) DB.
: , .
Datastore, , DB.
type Datastore interface {
AllBooks() ([]*Book, error)
}
. .
File: main.go
package main
import (
"fmt"
"log"
"net/http"
"bookstore/models"
)
type Env struct {
db models.Datastore
}
func main() {
db, err := models.NewDB("postgres://user:pass@localhost/bookstore")
if err != nil {
log.Panic(err)
}
env := &Env{db}
http.HandleFunc("/books", env.booksIndex)
http.ListenAndServe(":3000", nil)
}
func (env *Env) booksIndex(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, http.StatusText(405), 405)
return
}
bks, err := env.db.AllBooks()
if err != nil {
http.Error(w, http.StatusText(500), 500)
return
}
for _, bk := range bks {
fmt.Fprintf(w, "%s, %s, %s, lb%.2f\n", bk.Isbn, bk.Title, bk.Author, bk.Price)
}
}
File: models/db.go
package models
import (
_ "github.com/lib/pq"
"database/sql"
)
type Datastore interface {
AllBooks() ([]*Book, error)
}
type DB struct {
*sql.DB
}
func NewDB(dataSourceName string) (*DB, error) {
db, err := sql.Open("postgres", dataSourceName)
if err != nil {
return nil, err
}
if err = db.Ping(); err != nil {
return nil, err
}
return &DB{db}, nil
}
File: models/books.go
package models
type Book struct {
Isbn string
Title string
Author string
Price float32
}
func (db *DB) AllBooks() ([]*Book, error) {
rows, err := db.Query("SELECT * FROM books")
if err != nil {
return nil, err
}
defer rows.Close()
bks := make([]*Book, 0)
for rows.Next() {
bk := new(Book)
err := rows.Scan(&bk.Isbn, &bk.Title, &bk.Author, &bk.Price)
if err != nil {
return nil, err
}
bks = append(bks, bk)
}
if err = rows.Err(); err != nil {
return nil, err
}
return bks, nil
}
- , Datastore, .
package main
import (
"bookstore/models"
"net/http"
"net/http/httptest"
"testing"
)
type mockDB struct{}
func (mdb *mockDB) AllBooks() ([]*models.Book, error) {
bks := make([]*models.Book, 0)
bks = append(bks, &models.Book{"978-1503261969", "Emma", "Jayne Austen", 9.44})
bks = append(bks, &models.Book{"978-1505255607", "The Time Machine", "H. G. Wells", 5.99})
return bks, nil
}
func TestBooksIndex(t *testing.T) {
rec := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/books", nil)
env := Env{db: &mockDB{}}
http.HandlerFunc(env.booksIndex).ServeHTTP(rec, req)
expected := "978-1503261969, Emma, Jayne Austen, lb9.44\n978-1505255607, The Time Machine, H. G. Wells, lb5.99\n"
if expected != rec.Body.String() {
t.Errorf("\n...expected = %v\n...obtained = %v", expected, rec.Body.String())
}
}
- . x/net/context.
. x/net/context :
, API, .
, . , , .
File: main.go
package main
import (
"bookstore/models"
"fmt"
"golang.org/x/net/context"
"log"
"net/http"
)
type ContextHandler interface {
ServeHTTPContext(context.Context, http.ResponseWriter, *http.Request)
}
type ContextHandlerFunc func(context.Context, http.ResponseWriter, *http.Request)
func (h ContextHandlerFunc) ServeHTTPContext(ctx context.Context, rw http.ResponseWriter, req *http.Request) {
h(ctx, rw, req)
}
type ContextAdapter struct {
ctx context.Context
handler ContextHandler
}
func (ca *ContextAdapter) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
ca.handler.ServeHTTPContext(ca.ctx, rw, req)
}
func main() {
db, err := models.NewDB("postgres://user:pass@localhost/bookstore")
if err != nil {
log.Panic(err)
}
ctx := context.WithValue(context.Background(), "db", db)
http.Handle("/books", &ContextAdapter{ctx, ContextHandlerFunc(booksIndex)})
http.ListenAndServe(":3000", nil)
}
func booksIndex(ctx context.Context, w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, http.StatusText(405), 405)
return
}
bks, err := models.AllBooks(ctx)
if err != nil {
http.Error(w, http.StatusText(500), 500)
return
}
for _, bk := range bks {
fmt.Fprintf(w, "%s, %s, %s, lb%.2f\n", bk.Isbn, bk.Title, bk.Author, bk.Price)
}
}
File: models/db.go
package models
import (
"database/sql"
_ "github.com/lib/pq"
)
func NewDB(dataSourceName string) (*sql.DB, error) {
db, err := sql.Open("postgres", dataSourceName)
if err != nil {
return nil, err
}
if err = db.Ping(); err != nil {
return nil, err
}
return db, nil
}
File: models/books.go
package models
import (
"database/sql"
"errors"
"golang.org/x/net/context"
)
type Book struct {
Isbn string
Title string
Author string
Price float32
}
func AllBooks(ctx context.Context) ([]*Book, error) {
db, ok := ctx.Value("db").(*sql.DB)
if !ok {
return nil, errors.New("models: could not get database connection pool from context")
}
rows, err := db.Query("SELECT * FROM books")
if err != nil {
return nil, err
}
defer rows.Close()
bks := make([]*Book, 0)
for rows.Next() {
bk := new(Book)
err := rows.Scan(&bk.Isbn, &bk.Title, &bk.Author, &bk.Price)
if err != nil {
return nil, err
}
bks = append(bks, bk)
}
if err = rows.Err(); err != nil {
return nil, err
}
return bks, nil
}
P.S. .