囲碁・アイリス・ボルトを用いたURL短縮サービス



囲碁・アイリス・ボルトを用いたURL短縮サービス
あなたがTwitterやGoogle +のようなソーシャルメディアサイトに従うならば、あなたは私たちが良い友好的な短縮されたものを使用する完全なURLを使用するよりむしろそれに気づいたでしょうt.co/HurCcP0nn9 and bit.ly/iriscandothat1 .
あなた自身のドメイン内の独自の短縮URLを持っていることは素晴らしいことではないだろうか?
まず基礎を復習しましょう.

道具
プログラミング言語は私たちのためだけのツールですが、私たちのサービスに電力を供給するための安全で高速かつ“クロスプラットフォーム”プログラミング言語が必要です.
Gorapidly growing シンプルで高速かつ信頼性の高いソフトウェアを構築するために設計されたオープンソースプログラミング言語.見るhere どの偉大な会社を使用してサービスに電力を供給します.

GOプログラミング言語
ダウンロードとインストールの詳細についてはhere .

The article does not contain an introduction to the language itself, if you’re a newcomer I recommend you to bookmark this article, learn the language’s fundamentals and come back later on.



依存関係
多くの記事は、過去には、リードユーザーは“悪い”ので、Webフレームワークを使用しないように書かれている.私は、そのようなものがないとあなたに言わなければなりません、それは常にあなたが使用するつもりである(ウェブ)フレームワークに依存します.生産レベルでは、通常、我々は大きな会社のアプリケーションで使用するすべてのコードをする時間がない.短期的には:良いフレームワークは、任意の開発者、企業やスタートアップと不良のための便利なツールですが、時間の無駄は、明確かつ簡単です.
必要な依存関係は二つあります.
  • Iris , Webフレームワーク
  • Bolt (now bbolt) , 組み込みキー/値データベース
  • UUID ライブラリは、短いURLを生成するのに役立ちます
  • goパッケージのインストールは簡単な作業で、端末を開いて次のコマンドを実行します.
    $ go get github.com/kataras/iris/v12@latest
    $ go get -u github.com/etcd-io/bbolt
    $ go get -u github.com/satori/go.uuid
    

    最良の部分
    良い、もし我々が同じページにいるなら、それは我々が展開するのが簡単で、さらに拡張することができるURL短縮サーバーを作成する方法を学ぶ時間です!
    短いリンクを作成するには、ランダムな文字列を生成し、キーをキーとして元のURLを格納するために使用します.短縮URLに「get」リクエストが発行されると、オリジナルのURLをボルトから取得します.そのような値が存在するならば、我々はリダイレクトします.

    For the sake of simplicity let’s say that the project is located at $GOPATH/src/you/shortener *directory and the package name is *main.


    私たちのフロントエンドは馬鹿馬鹿しい単純です、それはちょうどインデックスページとその「スタイル」を含みます、テンプレートは/に位置しますテンプレートフォルダとスタイルCSSフォルダ.
    <html>
    
    <head>
        <meta charset="utf-8">
        <title>Golang URL Shortener</title>
        <link rel="stylesheet" href="/static/css/style.css" />
    </head>
    
    <body>
        <h2>Golang URL Shortener</h2>
        <h3>{{ .FORM_RESULT}}</h3>
        <form action="/shorten" method="POST">
            <input type="text" name="url" style="width: 35em;" />
            <input type="submit" value="Shorten!" />
        </form>
        {{ if IsPositive .URL_COUNT }}
            <p>{{ .URL_COUNT }} URLs shortened</p>
        {{ end }}
    
        <form action="/clear_cache" method="POST">
            <input type="submit" value="Clear DB" />
        </form>
    </body>
    
    </html>
    
    body{
        background-color:silver;
    }
    
    データベース* *レベルにまっすぐに移動すると、*保存**短縮URL(キー)とその元/完全なURL(値)、* *を取得することができる簡単な実装を作成します* *キーに基づいて完全なURLとデータベースへのすべての登録URLの合計数を返します.
    
    package main
    
    import (
        "bytes"
    
        "github.com/etcd-io/bbolt"
    )
    
    // 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 *bbolt.DB
    }
    
    var _ Store = &DB{}
    
    // openDatabase open a new database connection
    // and returns its instance.
    func openDatabase(stumb string) *bbolt.DB {
        // Open the data(base) file in the current working directory.
        // It will be created if it doesn't exist.
        db, err := bbolt.Open(stumb, 0600, nil)
        if err != nil {
            Panic(err)
        }
    
        // create the buckets here
        var tables = [...][]byte{
            tableURLs,
        }
    
        db.Update(func(tx *bbolt.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 *bbolt.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 *bbolt.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 *bbolt.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 *bbolt.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 *bbolt.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)
        }
    }
    
    私たちのための短縮URLを生成する当社の工場を作成しましょう!
    package main
    
    import (
        "net/url"
    
        "github.com/satori/go.uuid"
    )
    
    // Generator the type to generate keys(short urls)
    type Generator func() string
    
    // DefaultGenerator is the defautl url generator
    var DefaultGenerator = func() string {
        id, _ := uuid.NewV4()
        return id.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
    }
    
    我々は、私たちの小さなURL短縮サービスを提供するHTTPサーバーを実行するすべてのコンポーネントを接続し、結合するメインファイルを作成する必要があります.
    package main
    
    import (
        "html/template"
    
        "github.com/kataras/iris/v12"
    )
    
    func main() {
        // assign a variable to the DB so we can use its features later.
        db := NewDB("shortener.db")
        // Pass that db to our app, in order to be able to test the whole app with a different database later on.
        app := newApp(db)
    
        // release the "db" connection when server goes off.
        iris.RegisterOnInterrupt(db.Close)
    
        app.Run(iris.Addr(":8080"))
    }
    
    func newApp(db *DB) *iris.Application {
        app := iris.Default() // or app := iris.New()
    
        // create our factory, which is the manager for the object creation.
        // between our web app and the db.
        factory := NewFactory(DefaultGenerator, db)
    
        // serve the "./templates" directory's "*.html" files with the HTML std view engine.
        tmpl := iris.HTML("./templates", ".html").Reload(true)
        // register any template func(s) here.
        //
        // Look ./templates/index.html#L16
        tmpl.AddFunc("IsPositive", func(n int) bool {
            if n > 0 {
                return true
            }
            return false
        })
    
        app.RegisterView(tmpl)
    
        // Serve static files (css)
        app.StaticWeb("/static", "./resources")
    
        indexHandler := func(ctx iris.Context) {
            ctx.ViewData("URL_COUNT", db.Len())
            ctx.View("index.html")
        }
        app.Get("/", indexHandler)
    
        // find and execute a short url by its key
        // used on http://localhost:8080/u/dsaoj41u321dsa
        execShortURL := func(ctx iris.Context, key string) {
            if key == "" {
                ctx.StatusCode(iris.StatusBadRequest)
                return
            }
    
            value := db.Get(key)
            if value == "" {
                ctx.StatusCode(iris.StatusNotFound)
                ctx.Writef("Short URL for key: '%s' not found", key)
                return
            }
    
            ctx.Redirect(value, iris.StatusTemporaryRedirect)
        }
        app.Get("/u/{shortkey}", func(ctx iris.Context) {
            execShortURL(ctx, ctx.Params().Get("shortkey"))
        })
    
        app.Post("/shorten", func(ctx iris.Context) {
            formValue := ctx.FormValue("url")
            if formValue == "" {
                ctx.ViewData("FORM_RESULT", "You need to a enter a URL")
                ctx.StatusCode(iris.StatusLengthRequired)
            } else {
                key, err := factory.Gen(formValue)
                if err != nil {
                    ctx.ViewData("FORM_RESULT", "Invalid URL")
                    ctx.StatusCode(iris.StatusBadRequest)
                } else {
                    if err = db.Set(key, formValue); err != nil {
                        ctx.ViewData("FORM_RESULT", "Internal error while saving the URL")
                        app.Logger().Infof("while saving URL: " + err.Error())
                        ctx.StatusCode(iris.StatusInternalServerError)
                    } else {
                        ctx.StatusCode(iris.StatusOK)
                        shortenURL := "http://" + app.ConfigurationReadOnly().GetVHost() + "/u/" + key
                        ctx.ViewData("FORM_RESULT",
                            template.HTML("<pre><a target='_new' href='"+shortenURL+"'>"+shortenURL+" </a></pre>"))
                    }
    
                }
            }
    
            indexHandler(ctx) // no redirect, we need the FORM_RESULT.
        })
    
        app.Post("/clear_cache", func(ctx iris.Context) {
            db.Clear()
            ctx.Redirect("/")
        })
    
        return app
    }
    
    最後に!ビルドし、我々のアプリケーションを実行しましょう!
    $ cd $GOPATH/src/github.com/myname/url-shortener
    $ go build # build 
    $ ./url-shortener # run
    

    ボーナスセクション
    我々のアプリをテストすることは常に役に立つことです、したがって、私はあなたにアイリスでこれらのタイプのウェブアプリケーションをテストする方法に関する最初の考えを与えます.
    package main
    
    import (
        "io/ioutil"
        "os"
        "testing"
        "time"
    
        "github.com/kataras/iris/v12/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")
        if err != nil {
            t.Fatalf("creating temp file for database failed: %v", err)
        }
    
        db := NewDB(f.Name())
        app := newApp(db)
    
        e := httptest.New(t, app)
        originalURL := "https://google.com"
    
        // save
        e.POST("/shorten").
            WithFormField("url", originalURL).Expect().
            Status(httptest.StatusOK).Body().Contains("<pre><a target='_new' href=")
    
        keys := db.GetByValue(originalURL)
        if got := len(keys); got != 1 {
            t.Fatalf("expected to have 1 key but saved %d short urls", got)
        }
    
        // get
        e.GET("/u/" + keys[0]).Expect().
            Status(httptest.StatusTemporaryRedirect).Header("Location").Equal(originalURL)
    
        // save the same again, it should add a new key
        e.POST("/shorten").
            WithFormField("url", originalURL).Expect().
            Status(httptest.StatusOK).Body().Contains("<pre><a target='_new' href=")
    
        keys2 := db.GetByValue(originalURL)
        if got := len(keys2); got != 1 {
            t.Fatalf("expected to have 1 keys even if we save the same original url but saved %d short urls", got)
        } // the key is the same, so only the first one matters.
    
        if keys[0] != keys2[0] {
            t.Fatalf("expected keys to be equal if the original url is the same, but got %s = %s ", keys[0], keys2[0])
        }
    
        // clear db
        e.POST("/clear_cache").Expect().Status(httptest.StatusOK)
        if got := db.Len(); got != 0 {
            t.Fatalf("expected database to have 0 registered objects after /clear_cache but has %d", got)
        }
    
        // give it some time to release the db connection
        db.Close()
        time.Sleep(1 * time.Second)
        // close the file
        if err := f.Close(); err != nil {
            t.Fatalf("unable to close the file: %s", f.Name())
        }
    
        // and remove the file
        if err := os.Remove(f.Name()); err != nil {
            t.Fatalf("unable to remove the file from %s", f.Name())
        }
    
        time.Sleep(1 * time.Second)
    
    }
    
    テストの実行
    $ cd $GOPATH/src/github.com/myname/url-shortener
    $ go test -v # test
    

    それはすべて、かなり簡単です.正しい?
    このポストについてのあなたの考えを共有し、私はあなたが行く+虹で構築するために計画している素晴らしいアプリを教えてください!
    全記事を読むのに時間を割いてくれたおかげで、あなたの忍耐を称賛します.)

    The full source code is located here.
    If you have any further questions please feel free to leave a comment below or ask here!



    もう一度ありがとう
    私の媒体プロフィールとツイッターをチェックするのを忘れないでください、私もそこに若干のものを掲示しています

  • https://medium.com/@kataras