mux路由_使用大猩猩/ mux进行HTTP请求路由和验证

本文深入探讨了mux路由Go网络库的使用,包括http.ServeMux和gorilla/mux包的功能,展示了如何通过示例CrudWeb应用程序实现HTTP请求的多路复用和资源操作。文章还介绍了请求路由、验证和并发处理的最佳实践。

mux路由

Go网络库包括http.ServeMux结构类型,该结构类型支持HTTP请求多路复用(路由):Web服务器将对带有URI的托管资源的HTTP请求(如/ sales4today)路由到代码处理程序; 处理程序在发送HTTP响应(通常为HTML页面)之前执行适当的逻辑。 这是该架构的草图:


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

调用ListenAndServe方法以启动HTTP服务器

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

第二个参数nil表示DefaultServeMux用于请求路由。

gorilla/mux包具有mux.Router类型,以替代DefaultServeMux或自定义请求多路复用器。 在ListenAndServe调用中, mux.Router实例将nil替换为第二个参数。 通过代码示例可以很好地说明mux.Router如此吸引人的原因:

1.一个示例Crud Web应用程序

Crud Web应用程序(请参见下文)支持四个CRUD(创建读取更新删除)操作,这些操作与四个HTTP请求方法相匹配:分别为POST,GET,PUT和DELETE。 在crud应用程序中,托管资源是一列陈词滥调对,每个陈词滥调和一个冲突的陈词滥调,例如这对:

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

可以添加新的陈词滥调对,而现有的可以被编辑或删除。

Crud 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 ( " \n Listening 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 " \n Created: " + 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 = " \n Cliche 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 = " \n Cliche " + 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请求的响应,依此类推。 这种简化使得可以使用curl之类的命令行实用程序轻松测试应用程序,尤其是gorilla/mux组件。

可以从GitHub安装gorilla/mux软件包。 Crud应用程序无限期运行; 因此,应使用Control-C或等效控件将其终止。 可在我的网站找到 crud应用程序的代码,以及自述文件和示例卷曲测试。

2.请求路由

mux.Router扩展了REST样式的路由,它对HTTP方法(例如GET)和URL末尾的URI或路径(例如/ cliches )给予同等的权重。 URI用作HTTP动词(方法)的名词。 例如,在HTTP请求中,诸如

 GET / cliches 

意味着获得所有陈词滥调对 ,而诸如

 POST / cliches 

表示从HTTP正文中的数据创建一个词条对

Crud Web应用程序中,有五个函数充当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

每个函数有两个参数:一个http.ResponseWriter用于发送响应返回给请求者,并且一个指向http.Request ,它封装从底层HTTP请求信息。 gorilla/mux软件包使向Web服务器注册这些请求处理程序以及执行基于正则表达式的验证变得容易。

Crud应用程序中的startServer函数注册了请求处理程序。 考虑这对注册,将router作为mux.Router实例:


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

这些语句意味着对单个斜杠// ClichesAll的GET请求应路由到ClichesAll函数,然后由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 .

这三个陈词滥调对是Crud应用程序中的初始数据。

在这对注册声明中


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

URI相同( / cliches ),但动词不同:第一种情况下为GET,第二种情况下为POST。 该注册示例了REST风格的路由,因为仅动词之间的差异就足以将请求分派给两个不同的处理程序。

注册中允许使用多个HTTP方法,尽管这会束缚REST风格路由的精神:

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

HTTP请求可以在动词和URI之外的功能上进行路由。 例如注册

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

要求HTTPS访问POST请求才能创建新的陈词滥调对。 以类似的方式,注册可能要求请求具有指定的HTTP标头元素(例如,身份验证凭据)。

3.要求验证

gorilla/mux软件包采用一种简单直观的方法来通过正则表达式请求验证。 考虑此请求处理程序进行“ 获得一个”操作:

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

此注册排除了HTTP请求,例如

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

因为foo不是十进制数字。 该请求将产生熟悉的404(未找到)状态代码。 在此处理程序注册中包括正则表达式模式可确保仅当请求URI以十进制整数值结尾时,才调用ClichesOne函数来处理请求:

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

再举一个例子,考虑一下请求

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

此请求导致状态代码为405(错误方法),因为/ cliches URI仅在crud应用程序中注册,用于GET和POST请求。 与GET一个请求一样,PUT请求必须在URI的末尾包含数字ID:

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

4.并发问题

gorilla/mux路由器将对注册请求处理程序的每个调用作为单独的goroutine执行,这意味着并发已烘焙到程序包中。 例如,如果有十个同时请求,例如

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

然后mux.Router启动十个goroutine来执行ClichesCreate处理程序。

在五个请求操作GET all,GET one,POST,PUT和DELETE中,最后三个更改请求的资源,即clichesList陈词滥调对的共享clichesList 。 因此, crud应用程序需要通过协调对clichesList访问来确保安全的并发性。 用不同但等效的术语, crud应用程序必须阻止clichesList上的竞赛条件。 在生产环境中,可以使用数据库系统来存储诸如clichesList类的资源,然后可以通过数据库事务来管理安全并发。

Crud应用程序采用推荐的Go方法来实现安全并发​​:

  • 一旦Web服务器开始侦听请求,只有一个goroutine(在crud应用程序startServer函数中启动的资源管理器)就可以访问clichesList
  • 诸如ClichesCreateClichesAll类的请求处理程序将一个crudRequest实例(指向)发送到Go通道(默认情况下是线程安全的),并且资源管理器仅从该通道读取。 然后,资源管理器在clichesList上执行请求的操作。

安全并发体系结构可以概述如下:


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

使用这种架构,不需要显式锁定clichesList ,因为一旦CRUD请求开始进入,只有一个goroutine,即资源管理器访问clichesList

为了使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" )
}
ClichesCreate调用实用程序函数getDataFromRequest ,该函数从POST请求中提取新的陈词滥调和ClichesCreate 。 然后, ClichesCreate函数创建一个新的ClichePair ,设置两个字段,并创建一个crudRequest发送给单个资源管理器。 该请求包括一个确认通道,资源管理器使用该通道将信息返回给请求处理程序。 由于尚未访问clichesList因此可以在不涉及资源管理器的情况下完成所有设置工作。

ClichesCreate函数和其他请求处理程序的末尾调用的completeRequest实用程序函数

 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 " \n Created: " + cp . Cliche + " " + cp . Counter + " \n "
}

资源管理器为其他CRUD操作调用类似的实用程序功能。 值得重复的是,一旦Web服务器开始接受请求,资源管理器是读取或写入clichesList的唯一goroutine。

对于任何类型的Web应用程序, gorilla/mux软件包都以直观,直观的API提供请求路由,请求验证和相关服务。 Crud Web应用程序突出显示了程序包的主要功能。 给包裹试驾,您很可能会成为买家。

翻译自: https://opensource.com/article/18/8/http-request-routing-validation-gorillamux

mux路由

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值