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)
}
ミドルウェア関数は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のテンプレートパッケージを簡単に紹介することのみを目的としています。より複雑なユースケースに興味がある場合は、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で開発する過程で一般的なタスクに取り組む方法。
読んでくれてありがとう、そして幸せなコーディング!
-
チャットボット:ウェブ/アプリ開発の未来
チャットツールとロボットの融合により、Chatbot が形成されました。この概念は数年前に登場しましたが、最近では増加傾向にあり、無数の企業で広く採用されています。では、チャットボットについてはどうでしょうか? ボットは基本的に、実際の人間と同じように会話する仮想ロボット サービスです。彼らは決して苦しむことはなく、あなたの命令に従い続けます。チャットボットは AI と ML に従って会話のリズムを収集するため、人間の会話をコピーし、予測と思考を理解し、それに応じて反応することができます。したがって、会話チャットボットが多ければ多いほど、よりインテリジェントになります。いくつかの企業は、ユー
-
Bluefish と KompoZer で簡単にできるウェブ開発
ウェブサイトを構築していますか?どこから始めればよいかわかりませんか?手伝わせてください。ウェブサイトをゼロから作成することは真剣なビジネスです。アイデアが必要です。設計が必要です。また、サイトを構築するためのツールが必要です。次に、実際に作業を開始する前に、Web の言語である HTML に精通する必要があります。 必ずしも。 HTML、XHTML、CSS、またはその他の関連言語の達人でなくても、妥当な Web サイトを構築することは可能です。これらの少なくとも 1 つに習熟していることは確かに役立ちますが、ほんの一握りの熱意から始めることができます。 ミッション この目的