Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

GoでのWeb開発:ミドルウェア、テンプレート、データベースなど

このシリーズの前回の記事では、Go net / httpについて広範な議論を行いました。 パッケージと、本番環境に対応したWebアプリケーションでの使用方法。主にルーティングの側面とその他の癖や http.ServeMuxの機能に焦点を当てました。 タイプ。

この記事では、 ServeMuxに関するディスカッションを締めくくります。 デフォルトのルーターを使用してミドルウェア機能を実装する方法を示し、Goを使用してWebサービスを開発するときに役立つ他の標準ライブラリパッケージを紹介します。

Goのミドルウェア

多くのまたはすべてのHTTPリクエストに対して実行する必要のある共有機能を設定する方法は、ミドルウェアと呼ばれます。 。認証、ログ記録、Cookie検証などの一部の操作は、通常のルートハンドラーの前後で独立してリクエストに対応するミドルウェア関数として実装されることがよくあります。

Goでミドルウェアを実装するには、http.Handlerインターフェースを満たすタイプがあることを確認する必要があります。通常、これは、signature ServeHTTP(http.ResponseWriter、* http.Request)を使用してメソッドをアタッチする必要があることを意味します。 タイプに。このメソッドを使用する場合、どのタイプでも http.Handlerを満たします。 インターフェイス。

簡単な例を次に示します。

package main

import "net/http"

type helloHandler struct {
    name string
}

func (h helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello " + h.name))
}

func main() {
    mux := http.NewServeMux()

    helloJohn := helloHandler{name: "John"}
    mux.Handle("/john", helloJohn)
    http.ListenAndServe(":8080", mux)
}

/ johnに送信されたリクエスト ルートはhelloHandler.ServeHTTPに直接渡されます 方法。サーバーを起動してhttp:// localhost:8080 / johnにアクセスすると、これが実際に動作していることを確認できます。

ServeHTTPを追加する必要があります http.Handlerを実装するたびにカスタムタイプへのメソッド かなり面倒なので、 net / http パッケージはhttp.HandlerFuncを提供します タイプ。通常の関数をHTTPハンドラーとして使用できます。

関数に次のシグネチャがあることを確認するだけです: func(http.ResponseWriter、* http.Request);次に、それを http.HandlerFuncに変換します タイプ。

package main

import "net/http"

func helloJohnHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello John"))
}

func main() {
    mux := http.NewServeMux()
    mux.Handle("/john", http.HandlerFunc(helloJohnHandler))
    http.ListenAndServe(":8080", mux)
}

mux.Handleを置き換えることもできます mainの行 上記の関数withmux.HandleFunc 関数を直接渡します。前回の記事でこのパターンを排他的に使用しました。

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/john", helloJohnHandler)
    http.ListenAndServe(":8080", mux)
}

この時点で、 main に名前を設定できた以前とは異なり、名前は文字列にハードコードされています。 ハンドラーを呼び出す前に関数。この制限を取り除くために、以下に示すように、ハンドラーロジックをクロージャーに入れることができます:

package main

import "net/http"

func helloHandler(name string) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello " + name))
    })
}

func main() {
    mux := http.NewServeMux()
    mux.Handle("/john", helloHandler("John"))
    http.ListenAndServe(":8080", mux)
}

helloHandler 関数自体がhttp.Handlerを満たしていません インターフェイスですが、匿名関数を作成して返します。この関数はnameを閉じます パラメータ。これは、呼び出されたときにいつでもアクセスできることを意味します。この時点で、 helloHandler 関数は、必要な数の異なる名前に再利用できます。

では、これはすべてミドルウェアと何の関係があるのでしょうか?さて、ミドルウェア関数の作成は、上記で見たのと同じ方法で行われます。 (例のように)文字列をクロージャーに渡す代わりに、チェーン内の次のハンドラーを引数として渡すことができます。

完全なパターンは次のとおりです。

func middleware(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // Middleware logic goes here...
    next.ServeHTTP(w, r)
  })
}

ミドルウェア 上記の関数はハンドラーを受け入れてハンドラーを返します。無名関数をhttp.Handlerを満たすようにする方法に注意してください。 http.HandlerFuncにキャストしてインターフェース タイプ。匿名関数の終了時に、制御は nextに移されます ServeHTTP()を呼び出してハンドラー 方法。認証されたユーザーのIDなど、ハンドラー間で値を渡す必要がある場合は、 http.Request.Context()を使用できます。 Go1.7で導入された方法。

このパターンを簡単に示すミドルウェア関数を書いてみましょう。この関数は、 requestTimeというプロパティを追加します リクエストオブジェクトに送信されます。リクエストオブジェクトは、その後 helloHandlerによって使用されます。 リクエストのタイムスタンプを表示します。

package main

import (
    "context"
    "net/http"
    "time"
)

func requestTime(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ctx := r.Context()
        ctx = context.WithValue(ctx, "requestTime", time.Now().Format(time.RFC3339))
        r = r.WithContext(ctx)
        next.ServeHTTP(w, r)
    })
}

func helloHandler(name string) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        responseText := "<h1>Hello " + name + "</h1>"

        if requestTime := r.Context().Value("requestTime"); requestTime != nil {
            if str, ok := requestTime.(string); ok {
                responseText = responseText + "\n<small>Generated at: " + str + "</small>"
            }
        }
        w.Write([]byte(responseText))
    })
}

func main() {
    mux := http.NewServeMux()
    mux.Handle("/john", requestTime(helloHandler("John")))
    http.ListenAndServe(":8080", mux)
}

GoでのWeb開発:ミドルウェア、テンプレート、データベースなど

ミドルウェア関数はhttp.Handlerを受け入れて返すため タイプの場合、相互にネストされたミドルウェア関数の無限のチェーンを作成することが可能です。

たとえば、

mux := http.NewServeMux()
mux.Handle("/", middleware1(middleware2(appHandler)))

Aliceのようなライブラリを使用して、上記の構成を次のようなより読みやすい形式に変換できます。

alice.New(middleware1, middleware2).Then(appHandler)
テンプレート

テンプレートの使用は単一ページのアプリケーションの出現で衰退しましたが、それは完全なWeb開発ソリューションの重要な側面であり続けています。

Goは、すべてのテンプレートのニーズに対応する2つのパッケージを提供します: text / template およびhtml/ template 。どちらも同じインターフェースを備えていますが、後者はコードインジェクションの悪用を防ぐために舞台裏でエンコードを行います。

Goテンプレートは、世の中で最も表現力豊かなものではありませんが、うまく機能し、本番アプリケーションに使用できます。実際、人気のある静的サイトジェネレーターであるHugoは、テンプレートシステムの基盤となっています。

html / template がどのように機能するかを簡単に見てみましょう パッケージは、Webリクエストへの応答としてHTML出力を送信するために使用できます。

テンプレートの作成

index.htmlを作成します main.goと同じディレクトリにあるファイル ファイルを作成し、次のコードをファイルに追加します。

<ul>
  {{ range .TodoItems }}
  <li>{{ . }}</li>
  {{ end }}
</ul>

次に、次のコードを main.goに追加します ファイル:

package main

import (
    "html/template"
    "log"
    "os"
)

func main() {
    t, err := template.ParseFiles("index.html")
    if err != nil {
        log.Fatal(err)
    }

    todos := []string{"Watch TV", "Do homework", "Play games", "Read"}

    err = t.Execute(os.Stdout, todos)
    if err != nil {
        log.Fatal(err)
    }
}

上記のプログラムをgorun main.goで実行する場合 。次の出力が表示されます:

<ul>
  <li>Watch TV</li>
  <li>Do homework</li>
  <li>Play games</li>
  <li>Read</li>
</ul>

おめでとう!最初のGoテンプレートを作成しました。テンプレートファイルで使用した構文の簡単な説明は次のとおりです。

  • Goは二重中括弧を使用します( {{ および}} )データの評価と制御構造(アクションとして知られる)を区切る )テンプレート内。
  • 範囲 アクションとは、スライスなどのデータ構造を反復処理する方法です。
  • 現在のコンテキストを表します。 範囲内 アクション、現在のコンテキストは todosのスライスです 。ブロック内で、{{。 }} スライス内の各要素を参照します。

main.go内 ファイル、 template.ParseFiles メソッドは、1つ以上のファイルから新しいテンプレートを作成するために使用されます。その後、このテンプレートは template.Executeを使用して実行されます。 方法; io.Writerが必要です およびテンプレートに適用されるデータ。

上記の例では、テンプレートは標準出力に対して実行されますが、 io.Writer を満たす限り、任意の宛先に対して実行できます。 インターフェース。たとえば、出力をWebリクエストの一部として返したい場合は、テンプレートを ResponseWriterに実行するだけです。 以下に示すように、インターフェース。

package main

import (
    "html/template"
    "log"
    "net/http"
)

func main() {
    t, err := template.ParseFiles("index.html")
    if err != nil {
        log.Fatal(err)
    }

    todos := []string{"Watch TV", "Do homework", "Play games", "Read"}

    http.HandleFunc("/todos", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "text/html")
        err = t.Execute(w, todos)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }
    })
    http.ListenAndServe(":8080", nil)
}

GoでのWeb開発:ミドルウェア、テンプレート、データベースなど

このセクションは、Goのテンプレートパッケージを簡単に紹介することのみを目的としています。より複雑なユースケースに興味がある場合は、text/templateとhtml/templateのドキュメントを確認してください。

Goがテンプレートを作成する方法が好きでない場合は、ぬいぐるみライブラリなどの代替手段があります。

JSONの操作

JSONオブジェクトを操作する必要がある場合は、Goの標準ライブラリに encoding / jsonを介してJSONを解析およびエンコードするために必要なすべてのものが含まれていると聞いて喜ぶでしょう。 パッケージ。

デフォルトのタイプ

GoでJSONオブジェクトをエンコードまたはデコードする場合、次のタイプが使用されます。

  • ブール JSONブール値の場合
  • float64 JSON番号の場合
  • 文字列 JSON文字列の場合
  • nil JSONnullの場合
  • map [string] interface {} JSONオブジェクトの場合、および
  • [] interface {} JSON配列の場合。
エンコーディング

データ構造をJSONとしてエンコードするには、 json.Marshal 関数が使用されます。ここに例があります:

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    FirstName string
    LastName  string
    Age       int
    email     string
}

func main() {
    p := Person{
        FirstName: "Abraham",
        LastName:  "Freeman",
        Age:       100,
        email:     "[email protected]",
    }

    json, err := json.Marshal(p)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(string(json))
}

上記のプログラムには、 Personがあります。 4つの異なるフィールドを持つ構造体。 main 関数、 Personのインスタンス すべてのフィールドが初期化されて作成されます。 json.Marshal 次に、メソッドを使用して pを変換します 構造からJSONへ。このメソッドは、JSONデータにアクセスする前に処理する必要があるバイトのスライスまたはエラーを返します。

Goでバイトのスライスを文字列に変換するには、上記のように型変換を実行する必要があります。このプログラムを実行すると、次の出力が生成されます。

{"FirstName":"Abraham","LastName":"Freeman","Age":100}

ご覧のとおり、任意の方法で使用できる有効なJSONオブジェクトを取得します。 email に注意してください フィールドは結果から除外されます。これは、 Personからエクスポートされないためです。 小文字で始まることによるオブジェクト。

デフォルトでは、Goは構造体で結果のJSONオブジェクトのフィールド名と同じプロパティ名を使用します。ただし、これはstructfieldタグを使用して変更できます。

type Person struct {
    FirstName string `json:"first_name"`
    LastName  string `json:"last_name"`
    Age       int    `json:"age"`
    email     string `json:"email"`
}

上記のstructフィールドタグは、JSONエンコーダーが FirstNameをマップする必要があることを指定します first_nameへの構造体のプロパティ JSONオブジェクトのフィールドなど。前の例でのこの変更により、次の出力が生成されます。

{"first_name":"Abraham","last_name":"Freeman","age":100}
デコード

json.Unmarshal 関数は、JSONオブジェクトをGostructにデコードするために使用されます。次の署名があります:

func Unmarshal(data []byte, v interface{}) error

JSONデータのバイトスライスとデコードされたデータを保存する場所を受け入れます。デコードが成功した場合、返されるエラーは nilになります 。

次のJSONオブジェクトがあると仮定します

json := "{"first_name":"John","last_name":"Smith","age":35, "place_of_birth": "London", gender:"male"}"

Personのインスタンスにデコードできます 以下に示すように、構造体:

func main() {
    b := `{"first_name":"John","last_name":"Smith","age":35, "place_of_birth": "London", "gender":"male", "email": "[email protected]"}`
    var p Person
    err := json.Unmarshal([]byte(b), &p)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Printf("%+v\n", p)
}

そして、次の出力が得られます:

{FirstName:John LastName:Smith Age:35 email:}

Unmarshal 宛先タイプで見つかったフィールドのみをデコードします。この場合、 place_of_birth およびgender Person のどの構造体フィールドにもマップされないため、無視されます。 。この動作を利用して、大きなJSONオブジェクトから特定のフィールドをいくつか選択することができます。以前と同様に、宛先構造体のエクスポートされていないフィールドは、JSONオブジェクトに対応するフィールドがある場合でも影響を受けません。そのため、 email JSONオブジェクトに存在していても、出力には空の文字列が残ります。

データベース

database / sql パッケージは、SQL(またはSQLのような)データベースに関する汎用インターフェースを提供します。ここにリストされているものなどのデータベースドライバーと組み合わせて使用​​する必要があります。データベースドライバをインポートするときは、プレフィックスとしてアンダースコア _を付ける必要があります。 初期化する。

たとえば、MySQLdriverパッケージを database / sqlで使用する方法は次のとおりです。 :

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
)

内部的には、ドライバーは database / sqlで使用可能であると自分自身を登録します。 パッケージですが、コードで直接使用されることはありません。これにより、特定のドライバーへの依存を減らし、最小限の労力で別のドライバーに簡単に交換できるようになります。

データベース接続を開く

データベースにアクセスするには、 sql.DBを作成する必要があります 以下に示すように、オブジェクト:

func main() {
    db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/hello")
    if err != nil {
        log.Fatal(err)
    }
}

sql.Open メソッドは、後で使用するためにデータベースの抽象化を準備します。データベースへの接続を確立したり、接続パラメーターを検証したりすることはありません。データベースがすぐに利用可能でアクセス可能であることを確認したい場合は、 db.Ping()を使用してください 方法:

err = db.Ping()
if err != nil {
  log.Fatal(err)
}
データベース接続を閉じる

データベース接続を閉じるには、 db.Close()を使用できます 。通常は、延期する必要があります データベース接続を開いた関数(通常は main )が終了するまでデータベースを閉じます。 機能:

func main() {
    db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/hello")
    if err != nil {
        log.Fatal(err)
    }
  defer db.Close()
}

sql.DB オブジェクトは長持ちするように設計されているため、頻繁に開閉しないでください。その場合、接続の再利用と共有が不十分である、利用可能なネットワークリソースが不足している、散発的な障害が発生するなどの問題が発生する可能性があります。 sql.DBを渡すのが最善です メソッドを使用するか、グローバルに使用できるようにして、プログラムがそのデータストアへのアクセスを完了したときにのみ閉じます。

データベースからのデータの取得

テーブルのクエリは、3つのステップで実行できます。まず、 db.Query()を呼び出します 。次に、行を繰り返します。最後に、 rows.Scan()を使用します 各行を変数に抽出します。次に例を示します:

var (
    id int
    name string
)

rows, err := db.Query("select id, name from users where id = ?", 1)
if err != nil {
    log.Fatal(err)
}

defer rows.Close()

for rows.Next() {
    err := rows.Scan(&id, &name)
    if err != nil {
        log.Fatal(err)
    }

    log.Println(id, name)
}

err = rows.Err()
if err != nil {
    log.Fatal(err)
}

クエリが単一の行を返す場合は、 db.QueryRowを使用できます db.Queryの代わりにメソッド 前のコードスニペットの長い定型コードの一部を避けてください:

var (
    id int
    name string
)

err = db.QueryRow("select id, name from users where id = ?", 1).Scan(&id, &name)
if err != nil {
    log.Fatal(err)
}

fmt.Println(id, name)

NoSQLデータベース

Goは、Redis、MongoDB、CassandraなどのNoSQLデータベースも適切にサポートしていますが、それらを操作するための標準インターフェイスは提供していません。特定のデータベースのドライバーパッケージに完全に依存する必要があります。いくつかの例を以下に示します。

  • https://github.com/go-redis/redis(Redisドライバー)
  • https://github.com/mongodb/mongo-go-driver(MongoDBドライバー)。
  • https://github.com/gocql/gocql(Cassandraドライバー)。
  • https://github.com/Shopify/sarama(Apache Kafkaドライバー)
まとめ

この記事では、Goを使用してWebアプリケーションを構築するためのいくつかの重要な側面について説明しました。これで、多くのGoprogrammerが標準ライブラリで宣誓する理由を理解できるはずです。これは非常に包括的であり、本番環境に対応したサービスに必要なツールのほとんどを提供します。

ここで取り上げた内容について説明が必要な場合は、Twitterでメッセージを送ってください。このシリーズの次の最後の記事では、 goについて説明します。 ツールと、それを使用してGoで開発する過程で一般的なタスクに取り組む方法。

読んでくれてありがとう、そして幸せなコーディング!


  1. チャットボット:ウェブ/アプリ開発の未来

    チャットツールとロボットの融合により、Chatbot が形成されました。この概念は数年前に登場しましたが、最近では増加傾向にあり、無数の企業で広く採用されています。では、チャットボットについてはどうでしょうか? ボットは基本的に、実際の人間と同じように会話する仮想ロボット サービスです。彼らは決して苦しむことはなく、あなたの命令に従い続けます。チャットボットは AI と ML に従って会話のリズムを収集するため、人間の会話をコピーし、予測と思考を理解し、それに応じて反応することができます。したがって、会話チャットボットが多ければ多いほど、よりインテリジェントになります。いくつかの企業は、ユー

  2. Bluefish と KompoZer で簡単にできるウェブ開発

    ウェブサイトを構築していますか?どこから始めればよいかわかりませんか?手伝わせてください。ウェブサイトをゼロから作成することは真剣なビジネスです。アイデアが必要です。設計が必要です。また、サイトを構築するためのツールが必要です。次に、実際に作業を開始する前に、Web の言語である HTML に精通する必要があります。 必ずしも。 HTML、XHTML、CSS、またはその他の関連言語の達人でなくても、妥当な Web サイトを構築することは可能です。これらの少なくとも 1 つに習熟していることは確かに役立ちますが、ほんの一握りの熱意から始めることができます。 ミッション この目的