From 4229e5859acd856cf2903d0cfbb6f30c8b50b9e0 Mon Sep 17 00:00:00 2001 From: kataras Date: Tue, 11 Jul 2017 19:09:08 +0300 Subject: [PATCH] Split in three the _examples/tutorial/url-shortener and add the link of the updated article Former-commit-id: 23e21f5e6317c6f47f1ff8e0565480266d9006f5 --- README.md | 7 +- _examples/tutorial/url-shortener/factory.go | 50 ++++ _examples/tutorial/url-shortener/main.go | 252 +----------------- _examples/tutorial/url-shortener/main_test.go | 7 +- _examples/tutorial/url-shortener/shortener.go | 4 - _examples/tutorial/url-shortener/store.go | 184 +++++++++++++ learn.jpg.REMOVED.git-id | 1 + 7 files changed, 254 insertions(+), 251 deletions(-) create mode 100644 _examples/tutorial/url-shortener/factory.go delete mode 100644 _examples/tutorial/url-shortener/shortener.go create mode 100644 _examples/tutorial/url-shortener/store.go create mode 100644 learn.jpg.REMOVED.git-id diff --git a/README.md b/README.md index 65873bec..3c844b8a 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,10 @@ These types of projects need heart and sacrifices to continue offer the best dev ### 📑 Table of contents + + + + * [Installation](#-installation) * [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#mo-10-july-2017--v800) * [Learn](#-learn) @@ -58,7 +62,7 @@ These types of projects need heart and sacrifices to continue offer the best dev * [Miscellaneous](_examples/#miscellaneous) * [Typescript Automation Tools](typescript/#table-of-contents) * [Tutorial: Online Visitors](_examples/tutorial/online-visitors) - * [Tutorial: URL Shortener using BoltDB](_examples/tutorial/url-shortener) + * [Tutorial: URL Shortener using BoltDB](https://medium.com/@kataras/a-url-shortener-service-using-go-iris-and-bolt-4182f0b00ae7) * [Middleware](middleware/) * [Dockerize](https://github.com/iris-contrib/cloud-native-go) * [Philosophy](#-philosophy) @@ -74,6 +78,7 @@ The only requirement is the [Go Programming Language](https://golang.org/dl/), a ```sh $ go get -u github.com/kataras/iris +$ go get -u github.com/iris-contrib/middleware/... # useful handlers, optionally ``` > _iris_ takes advantage of the [vendor directory](https://docs.google.com/document/d/1Bz5-UB7g2uPBdOx-rw5t9MxJwkfpx90cqG9AFL0JAYo) feature. You get truly reproducible builds, as this method guards against upstream renames and deletes. diff --git a/_examples/tutorial/url-shortener/factory.go b/_examples/tutorial/url-shortener/factory.go new file mode 100644 index 00000000..9b893970 --- /dev/null +++ b/_examples/tutorial/url-shortener/factory.go @@ -0,0 +1,50 @@ +package main + +import ( + "github.com/satori/go.uuid" + "net/url" +) + +// Generator the type to generate keys(short urls) +type Generator func() string + +// DefaultGenerator is the defautl url generator +var DefaultGenerator = func() string { + return uuid.NewV4().String() +} + +// Factory is responsible to generate keys(short urls) +type Factory struct { + store Store + generator Generator +} + +// NewFactory receives a generator and a store and returns a new url Factory. +func NewFactory(generator Generator, store Store) *Factory { + return &Factory{ + store: store, + generator: generator, + } +} + +// Gen generates the key. +func (f *Factory) Gen(uri string) (key string, err error) { + // we don't return the parsed url because #hash are converted to uri-compatible + // and we don't want to encode/decode all the time, there is no need for that, + // we save the url as the user expects if the uri validation passed. + _, err = url.ParseRequestURI(uri) + if err != nil { + return "", err + } + + key = f.generator() + // Make sure that the key is unique + for { + if v := f.store.Get(key); v == "" { + break + } + key = f.generator() + } + + return key, nil +} diff --git a/_examples/tutorial/url-shortener/main.go b/_examples/tutorial/url-shortener/main.go index 2e247620..0c234588 100644 --- a/_examples/tutorial/url-shortener/main.go +++ b/_examples/tutorial/url-shortener/main.go @@ -1,22 +1,19 @@ -// Package main shows how you can create a simple URL SHortener. +// Package main shows how you can create a simple URL Shortener. +// +// Article: https://medium.com/@kataras/a-url-shortener-service-using-go-iris-and-bolt-4182f0b00ae7 // // $ go get github.com/boltdb/bolt/... -// $ go run main.go -// $ start http://localhost:8080 +// $ go get github.com/satori/go.uuid +// $ cd $GOPATH/src/github.com/kataras/iris/_examples/tutorial/url-shortener +// $ go build +// $ ./url-shortener package main import ( - "bytes" "html/template" - "net/url" "github.com/kataras/iris" "github.com/kataras/iris/context" - "github.com/kataras/iris/view" - - "github.com/boltdb/bolt" - - "github.com/satori/go.uuid" ) func main() { @@ -39,7 +36,7 @@ func newApp(db *DB) *iris.Application { factory := NewFactory(DefaultGenerator, db) // serve the "./templates" directory's "*.html" files with the HTML std view engine. - tmpl := view.HTML("./templates", ".html").Reload(true) + tmpl := iris.HTML("./templates", ".html").Reload(true) // register any template func(s) here. // // Look ./templates/index.html#L16 @@ -117,236 +114,3 @@ func newApp(db *DB) *iris.Application { return app } - -// +------------------------------------------------------------+ -// | | -// | Store | -// | | -// +------------------------------------------------------------+ - -// Panic panics, change it if you don't want to panic on critical INITIALIZE-ONLY-ERRORS -var Panic = func(v interface{}) { - panic(v) -} - -// Store is the store interface for urls. -// Note: no Del functionality. -type Store interface { - Set(key string, value string) error // error if something went wrong - Get(key string) string // empty value if not found - Len() int // should return the number of all the records/tables/buckets - Close() // release the store or ignore -} - -var ( - tableURLs = []byte("urls") -) - -// DB representation of a Store. -// Only one table/bucket which contains the urls, so it's not a fully Database, -// it works only with single bucket because that all we need. -type DB struct { - db *bolt.DB -} - -var _ Store = &DB{} - -// openDatabase open a new database connection -// and returns its instance. -func openDatabase(stumb string) *bolt.DB { - // Open the data(base) file in the current working directory. - // It will be created if it doesn't exist. - db, err := bolt.Open(stumb, 0600, nil) - if err != nil { - Panic(err) - } - - // create the buckets here - var tables = [...][]byte{ - tableURLs, - } - - db.Update(func(tx *bolt.Tx) (err error) { - for _, table := range tables { - _, err = tx.CreateBucketIfNotExists(table) - if err != nil { - Panic(err) - } - } - - return - }) - - return db -} - -// NewDB returns a new DB instance, its connection is opened. -// DB implements the Store. -func NewDB(stumb string) *DB { - return &DB{ - db: openDatabase(stumb), - } -} - -// Set sets a shorten url and its key -// Note: Caller is responsible to generate a key. -func (d *DB) Set(key string, value string) error { - return d.db.Update(func(tx *bolt.Tx) error { - b, err := tx.CreateBucketIfNotExists(tableURLs) - // Generate ID for the url - // Note: we could use that instead of a random string key - // but we want to simulate a real-world url shortener - // so we skip that. - // id, _ := b.NextSequence() - if err != nil { - return err - } - - k := []byte(key) - valueB := []byte(value) - c := b.Cursor() - - found := false - for k, v := c.First(); k != nil; k, v = c.Next() { - if bytes.Equal(valueB, v) { - found = true - break - } - } - // if value already exists don't re-put it. - if found { - return nil - } - - return b.Put(k, []byte(value)) - }) -} - -// Clear clears all the database entries for the table urls. -func (d *DB) Clear() error { - return d.db.Update(func(tx *bolt.Tx) error { - return tx.DeleteBucket(tableURLs) - }) -} - -// Get returns a url by its key. -// -// Returns an empty string if not found. -func (d *DB) Get(key string) (value string) { - keyB := []byte(key) - d.db.Update(func(tx *bolt.Tx) error { - b := tx.Bucket(tableURLs) - if b == nil { - return nil - } - c := b.Cursor() - for k, v := c.First(); k != nil; k, v = c.Next() { - if bytes.Equal(keyB, k) { - value = string(v) - break - } - } - - return nil - }) - - return -} - -// GetByValue returns all keys for a specific (original) url value. -func (d *DB) GetByValue(value string) (keys []string) { - valueB := []byte(value) - d.db.Update(func(tx *bolt.Tx) error { - b := tx.Bucket(tableURLs) - if b == nil { - return nil - } - c := b.Cursor() - // first for the bucket's table "urls" - for k, v := c.First(); k != nil; k, v = c.Next() { - if bytes.Equal(valueB, v) { - keys = append(keys, string(k)) - } - } - - return nil - }) - - return -} - -// Len returns all the "shorted" urls length -func (d *DB) Len() (num int) { - d.db.View(func(tx *bolt.Tx) error { - - // Assume bucket exists and has keys - b := tx.Bucket(tableURLs) - if b == nil { - return nil - } - - b.ForEach(func([]byte, []byte) error { - num++ - return nil - }) - return nil - }) - return -} - -// Close shutdowns the data(base) connection. -func (d *DB) Close() { - if err := d.db.Close(); err != nil { - Panic(err) - } -} - -// +------------------------------------------------------------+ -// | | -// | Factory | -// | | -// +------------------------------------------------------------+ - -// Generator the type to generate keys(short urls) -type Generator func() string - -// DefaultGenerator is the defautl url generator -var DefaultGenerator = func() string { - return uuid.NewV4().String() -} - -// Factory is responsible to generate keys(short urls) -type Factory struct { - store Store - generator Generator -} - -// NewFactory receives a generator and a store and returns a new url Factory. -func NewFactory(generator Generator, store Store) *Factory { - return &Factory{ - store: store, - generator: generator, - } -} - -// Gen generates the key. -func (f *Factory) Gen(uri string) (key string, err error) { - // we don't return the parsed url because #hash are converted to uri-compatible - // and we don't want to encode/decode all the time, there is no need for that, - // we save the url as the user expects if the uri validation passed. - _, err = url.ParseRequestURI(uri) - if err != nil { - return "", err - } - - key = f.generator() - // Make sure that the key is unique - for { - if v := f.store.Get(key); v == "" { - break - } - key = f.generator() - } - - return key, nil -} diff --git a/_examples/tutorial/url-shortener/main_test.go b/_examples/tutorial/url-shortener/main_test.go index b6f0216e..fa8002d5 100644 --- a/_examples/tutorial/url-shortener/main_test.go +++ b/_examples/tutorial/url-shortener/main_test.go @@ -3,13 +3,15 @@ package main import ( "io/ioutil" "os" - "time" - "testing" + "time" "github.com/kataras/iris/httptest" ) +// TestURLShortener tests the simple tasks of our url shortener application. +// Note that it's a pure test. +// The rest possible checks is up to you, take it as as an exercise! func TestURLShortener(t *testing.T) { // temp db file f, err := ioutil.TempFile("", "shortener") @@ -41,6 +43,7 @@ func TestURLShortener(t *testing.T) { e.POST("/shorten"). WithFormField("url", originalURL).Expect(). Status(httptest.StatusOK).Body().Contains("