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。 - 诸如
ClichesCreate和ClichesAll类的请求处理程序将一个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路由
本文深入探讨了mux路由Go网络库的使用,包括http.ServeMux和gorilla/mux包的功能,展示了如何通过示例CrudWeb应用程序实现HTTP请求的多路复用和资源操作。文章还介绍了请求路由、验证和并发处理的最佳实践。
1万+

被折叠的 条评论
为什么被折叠?



