GNU/Linux >> Belajar Linux >  >> Linux

Perutean dan validasi permintaan HTTP dengan gorila/mux

Pustaka jaringan Go menyertakan http.ServeMux tipe struktur, yang mendukung multiplexing permintaan HTTP (perutean):Server web merutekan permintaan HTTP untuk sumber daya yang dihosting, dengan URI seperti /sales4today , ke penangan kode; pawang melakukan logika yang sesuai sebelum mengirim respons HTTP, biasanya halaman HTML. Berikut sketsa arsitekturnya:

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

Dalam panggilan ke ListenAndServe metode untuk memulai server HTTP

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

argumen kedua dari nil berarti DefaultServeMux digunakan untuk perutean permintaan.

gorilla/mux paket memiliki mux.Router ketik sebagai alternatif dari DefaultServeMux atau multiplexer permintaan yang disesuaikan. Dalam ListenAndServe panggilan, mux.Router instance akan menggantikan nil sebagai argumen kedua. Apa yang membuat mux.Router jadi menarik paling baik ditunjukkan melalui contoh kode:

1. Contoh aplikasi web mentah

minyak mentah aplikasi web (lihat di bawah) mendukung empat operasi CRUD (Create Read Update Delete), yang cocok dengan empat metode permintaan HTTP:POST, GET, PUT, dan DELETE. Dalam mentah app, sumber daya yang dihosting adalah daftar pasangan klise, masing-masing klise dan klise yang saling bertentangan seperti pasangan ini:

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

Pasangan klise baru dapat ditambahkan, dan yang sudah ada dapat diedit atau dihapus.

minyak mentah aplikasi web

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)
   }
}

Untuk fokus pada perutean dan validasi permintaan, mentah aplikasi tidak menggunakan halaman HTML sebagai tanggapan atas permintaan. Sebaliknya, permintaan menghasilkan pesan respons teks biasa:Daftar pasangan klise adalah respons terhadap permintaan GET, konfirmasi bahwa pasangan klise baru telah ditambahkan ke daftar adalah respons terhadap permintaan POST, dan seterusnya. Penyederhanaan ini memudahkan pengujian aplikasi, khususnya gorilla/mux komponen, dengan utilitas baris perintah seperti curl .

gorilla/mux paket dapat diinstal dari GitHub. minyak mentah aplikasi berjalan tanpa batas waktu; karenanya, itu harus diakhiri dengan Control-C atau yang setara. Kode untuk mentah aplikasi, bersama dengan README dan contoh curl tes, tersedia di situs web saya.

2. Perutean permintaan

mux.Router memperluas perutean gaya REST, yang memberikan bobot yang sama pada metode HTTP (mis., GET) dan URI atau jalur di akhir URL (mis., /klise ). URI berfungsi sebagai kata benda untuk kata kerja HTTP (metode). Misalnya, dalam permintaan HTTP garis awal seperti

GET /cliches

artinya dapatkan semua pasangan klise , sedangkan garis awal seperti

POST /cliches

artinya membuat pasangan klise dari data di badan HTTP .

Dalam mentah aplikasi web, ada lima fungsi yang bertindak sebagai penangan permintaan untuk lima variasi permintaan HTTP:

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

Setiap fungsi membutuhkan dua argumen:sebuah http.ResponseWriter untuk mengirim tanggapan kembali ke pemohon, dan penunjuk ke http.Request , yang merangkum informasi dari permintaan HTTP yang mendasarinya. gorilla/mux package memudahkan untuk mendaftarkan penangan permintaan ini dengan server web, dan untuk melakukan validasi berbasis regex.

startServer fungsi di mentah aplikasi mendaftarkan penangan permintaan. Pertimbangkan pasangan pendaftaran ini, dengan router sebagai mux.Router contoh:

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

Pernyataan ini berarti bahwa permintaan GET untuk garis miring tunggal / atau /klise harus diarahkan ke ClichesAll fungsi, yang kemudian menangani permintaan. Misalnya, ikal permintaan (dengan % sebagai prompt baris perintah)

% curl --request GET localhost:8888/

menghasilkan respons ini:

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.

Ketiga pasangan klise tersebut merupakan data awal dalam mentah aplikasi.

Dalam pasangan pernyataan pendaftaran ini

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

URInya sama (/klise ) tetapi kata kerjanya berbeda:GET dalam kasus pertama, dan POST dalam kasus kedua. Registrasi ini mencontohkan perutean gaya REST karena perbedaan dalam kata kerja saja sudah cukup untuk mengirimkan permintaan ke dua penangan yang berbeda.

Lebih dari satu metode HTTP diperbolehkan dalam pendaftaran, meskipun ini melemahkan semangat perutean gaya REST:

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

Permintaan HTTP dapat dirutekan pada fitur selain kata kerja dan URI. Misalnya, pendaftaran

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

memerlukan akses HTTPS untuk permintaan POST untuk membuat pasangan klise baru. Dengan cara yang sama, pendaftaran mungkin memerlukan permintaan untuk memiliki elemen header HTTP tertentu (mis., kredensial autentikasi).

3. Minta validasi

gorilla/mux package mengambil pendekatan intuitif yang mudah untuk meminta validasi melalui ekspresi reguler. Pertimbangkan penangan permintaan ini untuk dapatkan satu operasi:

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

Registrasi ini mengesampingkan permintaan HTTP seperti

% curl --request GET localhost:8888/cliches/foo

karena foo bukan bilangan desimal. Permintaan menghasilkan kode status 404 (Tidak Ditemukan) yang sudah dikenal. Menyertakan pola regex dalam pendaftaran handler ini memastikan bahwa ClichesOne fungsi dipanggil untuk menangani permintaan hanya jika URI permintaan diakhiri dengan nilai bilangan bulat desimal:

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

Sebagai contoh kedua, pertimbangkan permintaan

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

Permintaan ini menghasilkan kode status 405 (Metode Buruk) karena /klise URI terdaftar, di mentah aplikasi, hanya untuk permintaan GET dan POST. Permintaan PUT, seperti permintaan GET one, harus menyertakan id numerik di akhir URI:

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

4. Masalah konkurensi

gorilla/mux router mengeksekusi setiap panggilan ke penangan permintaan terdaftar sebagai goroutine terpisah, yang berarti bahwa konkurensi dimasukkan ke dalam paket. Misalnya, jika ada sepuluh permintaan simultan seperti

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

lalu mux.Router meluncurkan sepuluh goroutine untuk menjalankan ClichesCreate penangan.

Dari lima operasi permintaan GET all, GET one, POST, PUT, dan DELETE, tiga yang terakhir mengubah sumber daya yang diminta, clichesList yang dibagikan yang menampung pasangan klise. Oleh karena itu, minyak mentah aplikasi perlu menjamin konkurensi yang aman dengan mengoordinasikan akses ke clichesList . Dalam istilah yang berbeda namun setara, mentah  aplikasi harus mencegah kondisi balapan di clichesList . Dalam lingkungan produksi, sistem database dapat digunakan untuk menyimpan sumber daya seperti clichesList , dan konkurensi yang aman kemudian dapat dikelola melalui transaksi basis data.

minyak mentah app mengambil pendekatan Go yang direkomendasikan untuk konkurensi yang aman:

  • Hanya satu goroutine, manajer sumber daya dimulai di mentah aplikasi startServer fungsi, memiliki akses ke clichesList setelah server web mulai mendengarkan permintaan.
  • Penangan permintaan seperti ClichesCreate dan ClichesAll kirim (penunjuk ke) crudRequest instance ke saluran Go (secara default aman untuk thread), dan pengelola sumber daya saja yang membaca dari saluran ini. Manajer sumber daya kemudian melakukan operasi yang diminta pada clichesList .

Arsitektur safe-concurrency dapat digambarkan sebagai berikut:

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

Dengan arsitektur ini, tidak ada penguncian eksplisit dari clichesList diperlukan karena hanya satu goroutine, pengelola sumber daya, yang mengakses clichesList setelah permintaan CRUD mulai masuk.

Untuk menjaga minyak mentah aplikasi secara bersamaan mungkin, penting untuk memiliki pembagian kerja yang efisien antara penangan permintaan, di satu sisi, dan manajer sumber daya tunggal, di sisi lain. Di sini, untuk ditinjau, adalah ClichesCreate penangan permintaan:

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")
}

Lebih banyak sumber daya Linux

  • Lembar contekan perintah Linux
  • Lembar contekan perintah Linux tingkat lanjut
  • Kursus online gratis:Ikhtisar Teknis RHEL
  • Lembar contekan jaringan Linux
  • Lembar contekan SELinux
  • Lembar contekan perintah umum Linux
  • Apa itu container Linux?
  • Artikel Linux terbaru kami

Pengendali permintaan ClichesCreate memanggil fungsi utilitas getDataFromRequest , yang mengekstrak klise dan kontra-klise baru dari permintaan POST. ClichesCreate fungsi kemudian membuat ClichePair baru , menyetel dua bidang, dan membuat crudRequest untuk dikirim ke manajer sumber daya tunggal. Permintaan ini mencakup saluran konfirmasi, yang digunakan pengelola sumber daya untuk mengembalikan informasi ke pengendali permintaan. Semua pekerjaan penyiapan dapat dilakukan tanpa melibatkan pengelola sumber daya karena clichesList belum diakses.

completeRequest fungsi utilitas dipanggil di akhir ClichesCreate fungsi dan penangan permintaan lainnya

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

membawa pengelola sumber daya ke dalam permainan dengan menempatkan crudRequest ke dalam crudRequests saluran:

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
}

Untuk permintaan POST, manajer sumber daya memanggil fungsi utilitas addPair , yang mengubah clichesList sumber:

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"
}

Manajer sumber daya memanggil fungsi utilitas serupa untuk operasi CRUD lainnya. Perlu diulangi bahwa resource manager adalah satu-satunya goroutine yang membaca atau menulis clichesList setelah server web mulai menerima permintaan.

Untuk aplikasi web jenis apa pun, gorilla/mux package menyediakan perutean permintaan, validasi permintaan, dan layanan terkait dalam API yang langsung dan intuitif. minyak mentah aplikasi web menyoroti fitur utama paket. Cobalah paket tersebut, dan kemungkinan besar Anda akan menjadi pembeli.


Linux
  1. Mengekstrak dan menampilkan data dengan awk

  2. Apa itu Perintah cURL Dan Bagaimana Cara Menggunakannya?

  3. Pangkas Dengan Lvm Dan Dm-crypt?

  1. Menjalankan Script Dengan “. ” Dan Dengan “sumber”?

  2. Permintaan HTTP(S) manual

  3. Temukan perbedaan dengan mtime - dan +

  1. strategi partisi dan subvol dengan btrfs

  2. AWK dan nama file dengan spasi di dalamnya.

  3. Apa itu sport dan dport?