GNU/Linux >> Linux の 問題 >  >> Linux

gorilla/muxを使用したHTTPリクエストのルーティングと検証

Goネットワークライブラリには、http.ServeMuxが含まれています HTTPリクエストの多重化(ルーティング)をサポートする構造タイプ:ウェブサーバーは、 / sales4today などのURIを使用して、ホストされたリソースのHTTPリクエストをルーティングします。 、コードハンドラーへ。ハンドラーは、HTTP応答(通常はHTMLページ)を送信する前に適切なロジックを実行します。アーキテクチャのスケッチは次のとおりです。

                 +------------+     +--------+     +---------+
HTTP request---->| web server |---->| router |---->| handler |
                 +------------+     +--------+     +---------+

ListenAndServeの呼び出しで HTTPサーバーを起動する方法

http.ListenAndServe(":8888", nil) // args: port & router

nilの2番目の引数 DefaultServeMuxを意味します リクエストルーティングに使用されます。

gorilla/mux パッケージにはmux.Routerがあります DefaultServeMuxの代わりに入力します またはカスタマイズされた要求マルチプレクサ。 ListenAndServeで 呼び出し、mux.Router インスタンスはnilを置き換えます 2番目の引数として。 mux.Routerの特徴 そのため、魅力的なものはコード例で最もよく示されます:

1。 CrudWebアプリのサンプル

crud Webアプリケーション(以下を参照)は、4つのCRUD(Create Read Update Delete)操作をサポートします。これらの操作は、それぞれPOST、GET、PUT、およびDELETEの4つのHTTP要求メソッドに一致します。 crud アプリの場合、ホストされるリソースは決まり文句のペアのリストであり、それぞれが決まり文句と、このペアなどの競合する決まり文句です。

Out of sight, out of mind. Absence makes the heart grow fonder.

新しいクリシェペアを追加したり、既存のクリシェペアを編集または削除したりできます。

crud ウェブアプリ

package main

import (
   "gorilla/mux"
   "net/http"
   "fmt"
   "strconv"
)

const GETALL string = "GETALL"
const GETONE string = "GETONE"
const POST string   = "POST"
const PUT string    = "PUT"
const DELETE string = "DELETE"

type clichePair struct {
   Id      int
   Cliche  string
   Counter string
}

// Message sent to goroutine that accesses the requested resource.
type crudRequest struct {
   verb     string
   cp       *clichePair
   id       int
   cliche   string
   counter  string
   confirm  chan string
}

var clichesList = []*clichePair{}
var masterId = 1
var crudRequests chan *crudRequest

// GET /
// GET /cliches
func ClichesAll(res http.ResponseWriter, req *http.Request) {
   cr := &crudRequest{verb: GETALL, confirm: make(chan string)}
   completeRequest(cr, res, "read all")
}

// GET /cliches/id
func ClichesOne(res http.ResponseWriter, req *http.Request) {
   id := getIdFromRequest(req)
   cr := &crudRequest{verb: GETONE, id: id, confirm: make(chan string)}
   completeRequest(cr, res, "read one")
}

// POST /cliches
func ClichesCreate(res http.ResponseWriter, req *http.Request) {
   cliche, counter := getDataFromRequest(req)
   cp := new(clichePair)
   cp.Cliche = cliche
   cp.Counter = counter
   cr := &crudRequest{verb: POST, cp: cp, confirm: make(chan string)}
   completeRequest(cr, res, "create")
}

// PUT /cliches/id
func ClichesEdit(res http.ResponseWriter, req *http.Request) {
   id := getIdFromRequest(req)
   cliche, counter := getDataFromRequest(req)
   cr := &crudRequest{verb: PUT, id: id, cliche: cliche, counter: counter, confirm: make(chan string)}
   completeRequest(cr, res, "edit")
}

// DELETE /cliches/id
func ClichesDelete(res http.ResponseWriter, req *http.Request) {
   id := getIdFromRequest(req)
   cr := &crudRequest{verb: DELETE, id: id, confirm: make(chan string)}
   completeRequest(cr, res, "delete")
}

func completeRequest(cr *crudRequest, res http.ResponseWriter, logMsg string) {
   crudRequests<-cr
   msg := <-cr.confirm
   res.Write([]byte(msg))
   logIt(logMsg)
}

func main() {
   populateClichesList()

   // From now on, this gorountine alone accesses the clichesList.
   crudRequests = make(chan *crudRequest, 8)
   go func() { // resource manager
      for {
         select {
         case req := <-crudRequests:
         if req.verb == GETALL {
            req.confirm<-readAll()
         } else if req.verb == GETONE {
            req.confirm<-readOne(req.id)
         } else if req.verb == POST {
            req.confirm<-addPair(req.cp)
         } else if req.verb == PUT {
            req.confirm<-editPair(req.id, req.cliche, req.counter)
         } else if req.verb == DELETE {
            req.confirm<-deletePair(req.id)
         }
      }
   }()
   startServer()
}

func startServer() {
   router := mux.NewRouter()

   // Dispatch map for CRUD operations.
   router.HandleFunc("/", ClichesAll).Methods("GET")
   router.HandleFunc("/cliches", ClichesAll).Methods("GET")
   router.HandleFunc("/cliches/{id:[0-9]+}", ClichesOne).Methods("GET")

   router.HandleFunc("/cliches", ClichesCreate).Methods("POST")
   router.HandleFunc("/cliches/{id:[0-9]+}", ClichesEdit).Methods("PUT")
   router.HandleFunc("/cliches/{id:[0-9]+}", ClichesDelete).Methods("DELETE")

   http.Handle("/", router) // enable the router

   // Start the server.
   port := ":8888"
   fmt.Println("\nListening on port " + port)
   http.ListenAndServe(port, router); // mux.Router now in play
}

// Return entire list to requester.
func readAll() string {
   msg := "\n"
   for _, cliche := range clichesList {
      next := strconv.Itoa(cliche.Id) + ": " + cliche.Cliche + "  " + cliche.Counter + "\n"
      msg += next
   }
   return msg
}

// Return specified clichePair to requester.
func readOne(id int) string {
   msg := "\n" + "Bad Id: " + strconv.Itoa(id) + "\n"

   index := findCliche(id)
   if index >= 0 {
      cliche := clichesList[index]
      msg = "\n" + strconv.Itoa(id) + ": " + cliche.Cliche + "  " + cliche.Counter + "\n"
   }
   return msg
}

// Create a new clichePair and add to list
func addPair(cp *clichePair) string {
   cp.Id = masterId
   masterId++
   clichesList = append(clichesList, cp)
   return "\nCreated: " + cp.Cliche + " " + cp.Counter + "\n"
}

// Edit an existing clichePair
func editPair(id int, cliche string, counter string) string {
   msg := "\n" + "Bad Id: " + strconv.Itoa(id) + "\n"
   index := findCliche(id)
   if index >= 0 {
      clichesList[index].Cliche = cliche
      clichesList[index].Counter = counter
      msg = "\nCliche edited: " + cliche + " " + counter + "\n"
   }
   return msg
}

// Delete a clichePair
func deletePair(id int) string {
   idStr := strconv.Itoa(id)
   msg := "\n" + "Bad Id: " + idStr + "\n"
   index := findCliche(id)
   if index >= 0 {
      clichesList = append(clichesList[:index], clichesList[index + 1:]...)
      msg = "\nCliche " + idStr + " deleted\n"
   }
   return msg
}

//*** utility functions
func findCliche(id int) int {
   for i := 0; i < len(clichesList); i++ {
      if id == clichesList[i].Id {
         return i;
      }
   }
   return -1 // not found
}

func getIdFromRequest(req *http.Request) int {
   vars := mux.Vars(req)
   id, _ := strconv.Atoi(vars["id"])
   return id
}

func getDataFromRequest(req *http.Request) (string, string) {
   // Extract the user-provided data for the new clichePair
   req.ParseForm()
   form := req.Form
   cliche := form["cliche"][0]    // 1st and only member of a list
   counter := form["counter"][0]  // ditto
   return cliche, counter
}

func logIt(msg string) {
   fmt.Println(msg)
}

func populateClichesList() {
   var cliches = []string {
      "Out of sight, out of mind.",
      "A penny saved is a penny earned.",
      "He who hesitates is lost.",
   }
   var counterCliches = []string {
      "Absence makes the heart grow fonder.",
      "Penny-wise and dollar-foolish.",
      "Look before you leap.",
   }

   for i := 0; i < len(cliches); i++ {
      cp := new(clichePair)
      cp.Id = masterId
      masterId++
      cp.Cliche = cliches[i]
      cp.Counter = counterCliches[i]
      clichesList = append(clichesList, cp)
   }
}

リクエストのルーティングと検証に焦点を当てるために、 crud アプリは、リクエストへの応答としてHTMLページを使用しません。代わりに、リクエストはプレーンテキストの応答メッセージになります。決まり文句のペアのリストはGETリクエストへの応答であり、新しい決まり文句のペアがリストに追加されたことの確認はPOSTリクエストへの応答です。この簡略化により、アプリ、特にgorilla/muxのテストが簡単になります。 curlなどのコマンドラインユーティリティを備えたコンポーネント 。

gorilla/mux パッケージはGitHubからインストールできます。 crud アプリは無期限に実行されます。したがって、Control-Cまたは同等のもので終了する必要があります。 crudのコード アプリ、READMEおよびサンプル curl テストは、私のWebサイトで入手できます。

2。ルーティングをリクエストする

mux.Router RESTスタイルのルーティングを拡張します。これにより、HTTPメソッド(例:GET)とURLの末尾のURIまたはパス(例: / cliches )に同等の重みが与えられます。 )。 URIは、HTTP動詞(メソッド)の名詞として機能します。たとえば、HTTPリクエストでは、

などのスタートラインがあります。
GET /cliches

すべての決まり文句のペアを取得する 、一方、

などのスタートライン
POST /cliches

HTTPボディのデータから決まり文句のペアを作成することを意味します 。

crud Webアプリには、HTTPリクエストの5つのバリエーションのリクエストハンドラーとして機能する5つの関数があります。

ClichesAll(...)    # GET: get all of the cliche pairs
ClichesOne(...)    # GET: get a specified cliche pair
ClichesCreate(...) # POST: create a new cliche pair
ClichesEdit(...)   # PUT: edit an existing cliche pair
ClichesDelete(...) # DELETE: delete a specified cliche pair

各関数は2つの引数を取ります:http.ResponseWriter リクエスターに応答を送り返すため、およびhttp.Requestへのポインター 、基になるHTTPリクエストからの情報をカプセル化します。 gorilla/mux パッケージを使用すると、これらのリクエストハンドラをウェブサーバーに簡単に登録し、正規表現ベースの検証を実行できます。

startServer crudで機能する appはリクエストハンドラーを登録します。 routerを使用したこの登録のペアについて考えてみます。 mux.Routerとして インスタンス:

router.HandleFunc("/", ClichesAll).Methods("GET")
router.HandleFunc("/cliches", ClichesAll).Methods("GET")

これらのステートメントは、単一のスラッシュ /のいずれかに対するGETリクエストを意味します。 または/cliches ClichesAllにルーティングする必要があります 次に、リクエストを処理する関数。たとえば、 curl リクエスト(コマンドラインプロンプトとして%を使用)

% curl --request GET localhost:8888/

この応答を生成します:

1: Out of sight, out of mind.  Absence makes the heart grow fonder.
2: A penny saved is a penny earned.  Penny-wise and dollar-foolish.
3: He who hesitates is lost.  Look before you leap.

3つの決まり文句のペアは、 crudの初期データです。 アプリ。

この登録届出書のペアでは

router.HandleFunc("/cliches", ClichesAll).Methods("GET")
router.HandleFunc("/cliches", ClichesCreate).Methods("POST")

URIは同じです( / cliches )ただし、動詞は異なります。最初のケースではGET、2番目のケースではPOSTです。この登録は、動詞の違いだけで2つの異なるハンドラーにリクエストをディスパッチするのに十分であるため、RESTスタイルのルーティングを例示しています。

1つの登録で複数のHTTPメソッドを使用できますが、これはRESTスタイルのルーティングの精神を損ないます:

router.HandleFunc("/cliches", DoItAll).Methods("POST", "GET")

HTTPリクエストは、動詞とURI以外の機能にルーティングできます。たとえば、登録

router.HandleFunc("/cliches", ClichesCreate).Schemes("https").Methods("POST")

新しいclicheペアを作成するには、POSTリクエストにHTTPSアクセスが必要です。同様に、登録では、指定されたHTTPヘッダー要素(認証クレデンシャルなど)を持つリクエストが必要になる場合があります。

3。検証をリクエストする

gorilla/mux packageは、正規表現を介して検証を要求するための簡単で直感的なアプローチを採用しています。 get oneのこのリクエストハンドラを検討してください 操作:

router.HandleFunc("/cliches/{id:[0-9]+}", ClichesOne).Methods("GET")

この登録により、

などのHTTPリクエストが除外されます。
% curl --request GET localhost:8888/cliches/foo

foo 10進数ではありません。リクエストの結果、おなじみの404(見つかりません)ステータスコードが表示されます。このハンドラー登録に正規表現パターンを含めると、ClichesOneが確実になります。 関数は、リクエストURIが10進整数値で終わる場合にのみリクエストを処理するために呼び出されます:

% curl --request GET localhost:8888/cliches/3  # ok

2番目の例として、リクエストについて考えてみましょう

% curl --request PUT --data "..." localhost:8888/cliches

/ cliches が原因で、このリクエストのステータスコードは405(不正な方法)になります。 URIはcrudに登録されています アプリ、GETおよびPOSTリクエストのみ。 PUTリクエストは、GET oneリクエストのように、URIの最後に数値IDを含める必要があります:

router.HandleFunc("/cliches/{id:[0-9]+}", ClichesEdit).Methods("PUT")

4。並行性の問題

gorilla/mux ルーターは、登録されたリクエストハンドラーへの各呼び出しを個別のゴルーチンとして実行します。これは、同時実行性がパッケージに組み込まれていることを意味します。たとえば、

のように10個の同時リクエストがある場合
% curl --request POST --data "..." localhost:8888/cliches

次に、mux.Router ClichesCreateを実行するために10個のゴルーチンを起動します ハンドラー。

5つのリクエスト操作GETall、GET one、POST、PUT、およびDELETEのうち、最後の3つは、リクエストされたリソースである共有clichesListを変更します。 決まり文句のペアを収容します。したがって、 crud アプリは、clichesListへのアクセスを調整することにより、安全な同時実行性を保証する必要があります 。異なるが同等の用語で、 crud アプリはclichesListの競合状態を防ぐ必要があります 。実稼働環境では、データベースシステムを使用して、clichesListなどのリソースを格納できます。 、そして安全な同時実行性は、データベーストランザクションを通じて管理できます。

crud アプリは、安全な同時実行性のために推奨されるGoアプローチを採用しています:

  • 単一のゴルーチン、リソースマネージャー crudで開始 アプリstartServer 関数、clichesListにアクセスできます Webサーバーがリクエストのリッスンを開始したら。
  • ClichesCreateなどのリクエストハンドラー およびClichesAll crudRequestへの(ポインタ)を送信します インスタンスをGoチャネル(デフォルトではスレッドセーフ)に切り替え、リソースマネージャーのみがこのチャネルから読み取ります。次に、リソースマネージャーは、clichesListで要求された操作を実行します。 。

安全な同時実行アーキテクチャは、次のようにスケッチできます。

                 crudRequest                   read/write
request handlers------------->resource manager------------>clichesList

このアーキテクチャでは、clichesListを明示的にロックする必要はありません。 clichesListにアクセスするのはリソースマネージャーの1つのゴルーチンだけなので、必要です。 CRUDリクエストが届き始めたら。

crudを維持するには アプリを可能な限り同時に使用するには、一方のリクエストハンドラーと、もう一方の単一のリソースマネージャーの間で効率的な分業を行うことが不可欠です。ここで、レビューのために、ClichesCreateがあります リクエストハンドラ:

func ClichesCreate(res http.ResponseWriter, req *http.Request) {
   cliche, counter := getDataFromRequest(req)
   cp := new(clichePair)
   cp.Cliche = cliche
   cp.Counter = counter
   cr := &crudRequest{verb: POST, cp: cp, confirm: make(chan string)}
   completeRequest(cr, res, "create")
}

その他のLinuxリソース

  • Linuxコマンドのチートシート
  • 高度なLinuxコマンドのチートシート
  • 無料のオンラインコース:RHELの技術概要
  • Linuxネットワーキングのチートシート
  • SELinuxチートシート
  • Linuxの一般的なコマンドのチートシート
  • Linuxコンテナとは何ですか?
  • 最新のLinux記事

リクエストハンドラーClichesCreate ユーティリティ関数getDataFromRequestを呼び出します 、POSTリクエストから新しい決まり文句と反決まり文句を抽出します。 ClichesCreate 次に、関数は新しいClichePairを作成します 、2つのフィールドを設定し、crudRequestを作成します 単一のリソースマネージャーに送信されます。この要求には、リソース・マネージャーが情報を要求ハンドラーに返すために使用する確認チャネルが含まれています。 clichesListにより、リソースマネージャーを使用せずにすべてのセットアップ作業を実行できます。 まだアクセスされていません。

completeRequest ClichesCreateの最後に呼び出されるユーティリティ関数 関数とその他のリクエストハンドラー

completeRequest(cr, res, "create") // shown above

crudRequestを配置することにより、リソースマネージャーを機能させます crudRequestsに チャネル:

func completeRequest(cr *crudRequest, res http.ResponseWriter, logMsg string) {
   crudRequests<-cr          // send request to resource manager
   msg := <-cr.confirm       // await confirmation string
   res.Write([]byte(msg))    // send confirmation back to requester
   logIt(logMsg)             // print to the standard output
}

POSTリクエストの場合、リソースマネージャーはユーティリティ関数addPairを呼び出します。 、clichesListを変更します リソース:

func addPair(cp *clichePair) string {
   cp.Id = masterId  // assign a unique ID
   masterId++        // update the ID counter
   clichesList = append(clichesList, cp) // update the list
   return "\nCreated: " + cp.Cliche + " " + cp.Counter + "\n"
}

リソースマネージャーは、他のCRUD操作に対して同様のユーティリティ関数を呼び出します。 clichesListを読み書きするための唯一のゴルーチンは、リソースマネージャーであることを繰り返す価値があります。 Webサーバーがリクエストの受け入れを開始したら。

あらゆるタイプのWebアプリケーションの場合、gorilla/mux パッケージは、リクエストのルーティング、リクエストの検証、および関連サービスをわかりやすく直感的なAPIで提供します。 crud ウェブアプリは、パッケージの主な機能を強調しています。パッケージを試してみると、購入者になる可能性があります。


Linux
  1. cURLコマンドとは何ですか?その使用方法は?

  2. mtime - と + で違いを見つける

  3. btrfs によるパーティショニングと subvol 戦略

  1. awkを使用したデータの抽出と表示

  2. LvmとDm-cryptでトリミングしますか?

  3. スポーツとdポートとは?

  1. 「でスクリプトを実行します。 」と「ソース」で?

  2. 手動 HTTP(S) リクエスト

  3. スペースを含む AWK およびファイル名。