Merge remote-tracking branch 'LCTT/master'

This commit is contained in:
Xingyu.Wang 2018-12-15 09:23:02 +08:00
commit af844428c7
4 changed files with 506 additions and 706 deletions

View File

@ -1,42 +1,49 @@
知识共享是怎样造福艺术家和大企业的
你所不知道的知识共享CC
======
> 知识共享为艺术家提供访问权限和原始素材。大公司也从中受益。
![](https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/CreativeCommons_ideas_520x292_1112JS.png?itok=otei0vKb)
我毕业于电影学院,毕业后在一所电影学校教书,之后进入一家主流电影工作室,我一直在从事电影相关的工作。创造业的方方面面面临着同一个问题:创造者需要原材料。有趣的是,自由文化运动提出了解决方案,具体来说是在自由文化运动中出现的知识共享组织。
我毕业于电影学院,毕业后在一所电影学校教书,之后进入一家主流电影工作室,我一直在从事电影相关的工作。创意产业的方方面面面临着同一个问题:创作者需要原材料。有趣的是,自由文化运动提出了解决方案,具体来说是在自由文化运动中出现的<ruby>知识共享<rt>Creative Commons</rt></ruby>组织。
###知识共享能够为我们提供展示片段和小样
### 知识共享能够为我们提供展示片段和小样
和其他事情一样,创造力也需要反复练习。幸运的是,在我刚开始接触电脑时,就在一本渲染工场的专业杂志中接触到了开源这个存在。当时我并不理解所谓的“开源”是什么,但我知道只有开源工具能帮助我在领域内稳定发展。对我来说,知识共享也是如此。知识共享可以为艺术家们提供充满丰富艺术资源的工作室。
和其他事情一样,创造力也需要反复练习。幸运的是,在我刚开始接触电脑时,就在一本关于渲染工场的专业杂志中接触到了开源这个存在。当时我并不理解所谓的“开源”是什么,但我知道只有开源工具能帮助我在领域内稳定发展。对我来说,知识共享也是如此。知识共享可以为艺术家们提供充满丰富艺术资源的工作室。
我在电影学院任教时,经常需要给学生们准备练习编辑、录音、拟音、分级、评分的脚本。在 Jim Munroe 的独立作品 [Infest Wisely][1] 中和 [Vimeo][2] 上的知识共享里我总能找到我想要的。这些写实的脚本覆盖内容十分广泛,从独立影院出品到昂贵的高质的升降镜头(一般都会用无人机代替)都有。
我在电影学院任教时,经常需要给学生们准备练习编辑、录音、拟音、分级、评分的示例录像。在 Jim Munroe 的独立作品 [Infest Wisely][1] 中和 [Vimeo][2] 上的知识共享内容里我总能找到我想要的。这些逼真的镜头覆盖内容十分广泛,从独立制作到昂贵的高品质的升降镜头(一般都会用无人机代替)都有。
对实验艺术来说,确有无尽可能。知识共享提供了丰富的底片材料,这些材料可以用来整合,混剪等等,可以满足一位视觉先锋能够想到的任何用途。
![](https://opensource.com/sites/default/files/u128651/bunny.png)
在接触知识共享之前,如果我想要使用写实脚本,我只能用之前的学生和老师拍摄的或者直接使用版权库里的脚本,但这些都有很多局限性
对实验主义艺术来说,确有无尽可能。知识共享提供了丰富的素材,这些材料可以用来整合、混剪等等,可以满足一位视觉先锋能够想到的任何用途
###坚守版权的底线很重要
在接触知识共享之前,如果我想要使用写实镜头,如果在大学,只能用之前的学生和老师拍摄的或者直接使用版权库里的镜头,或者使用有受限的版权保护的镜头。
知识共享同样能够创造经济效益。在某大型计算机公司的渲染工场工作时,我负责在某些硬件设施上测试渲染的运行情况,而这个测试时刻面临着被搁置的风险。做这些测试时,我用的都是[大雄兔][3]的资源因为这个电影和它的组件都是可以免费使用和分享的。如果没有这个小短片在接触写实资源之前我都没法完成我的实验因为对于一个计算机公司来说雇佣一只3D艺术家来应召布景是不太现实的。
### 坚守版权的底线很重要
令我震惊的是,与开源类似,知识共享已经用我们难以想象的方式支撑起了大公司。知识共享的使用或有或无地影响着公司的日常程序,但它填补了不足,让工作流程顺利进行。我没见到谁在他们的书中将流畅工作归功于知识共享的应用,但它确实无处不在
知识共享同样能够创造经济效益。在某大型计算机公司的渲染工场工作时,我负责在某些硬件设施上测试渲染的运行情况,而这个测试时刻面临着被搁置的风险。做这些测试时,我用的都是[大雄兔][3]的资源,因为这个电影和它的组件都是可以免费使用和分享的。如果没有这个小短片,在接触写实资源之前我都没法完成我的实验,因为对于一个计算机公司来说,雇佣一只 3D 艺术家来按需布景是不太现实的
我也见过一些开放版权的电影,比如[辛特尔][4],在最近的电视节目中播放了它的短片,那时的电视比现在的网络媒体要火得多
令我震惊的是,与开源类似,知识共享已经用我们难以想象的方式支撑起了大公司。知识共享的使用可能会也可能不会影响公司的日常流程,但它填补了不足,让工作流程顺利进行。我没见到谁在他们的书中将流畅工作归功于知识共享的应用,但它确实无处不在
###知识共享可以提供大量原材料
![](https://opensource.com/sites/default/files/u128651/sintel.png)
艺术家需要原材料。画家需要颜料,画笔和画布。雕塑家需要陶土和工具。数字内容编辑师需要数字内容,无论它是剪贴画还是音效或者是电子游戏里的成品精灵。
我也见过一些开放版权的电影,比如[辛特尔][4],在最近的电视节目中播放了它的短片,电视的分辨率已经超过了标准媒体。
### 知识共享可以提供大量原材料
艺术家需要原材料。画家需要颜料、画笔和画布。雕塑家需要陶土和工具。数字内容编辑师需要数字内容,无论它是剪贴画还是音效或者是电子游戏里的现成的精灵。
数字媒介赋予了人们超能力,让一个人就能完成需要一组人员才能完成的工作。事实上,我们大部分都好高骛远。我们想做高大上的项目,想让我们的成果不论是视觉上还是听觉上都无与伦比。我们想塑造的是宏大的世界,紧张的情节,能引起共鸣的作品,但我们所拥有的时间精力和技能与之都不匹配,达不到想要的效果。
是知识共享再一次拯救了我们,用 [Freesound.org][5], [Openclipart.org][6], [OpenGameArt.org][7] 等等网站上那些细小的开放版权艺术材料。通过知识共享,艺术家可以使用各种他们自己没办法创造的原材料,来完成他们原本完不成的工作。
是知识共享再一次拯救了我们,在 [Freesound.org][5]、 [Openclipart.org][6]、 [OpenGameArt.org][7] 等等网站上都有大量的开放版权艺术材料。通过知识共享,艺术家可以使用各种他们自己没办法创造的原材料,来完成他们原本完不成的工作。
最神奇的是,不用自己投资,你放在网上给大家使用的原材料就能变成精美的作品,而这是你从没想过的。我在知识共享上面分享了很多音乐素材,它们现在用于无数的专辑和电子游戏里。有些人用了我的材料会通知我,有些是我自己发现的,所以这些材料的应用可能比我知道的还有多得多。有时我会偶然看到我亲手画的标志出现在我从没听说过的软件里。我见到过我为[开源网站][8]写的文章在别处发表,有的是论文的参考文献,白皮书或者参考资料中。
最神奇的是,不用自己投资,你放在网上给大家使用的原材料就能变成精美的作品,而这是你从没想过的。我在知识共享上面分享了很多音乐素材,它们现在用于无数的专辑和电子游戏里。有些人用了我的材料会通知我,有些是我自己发现的,所以这些材料的应用可能比我知道的还有多得多。有时我会偶然看到我亲手画的标志出现在我从没听说过的软件里。我见到过我为 [Opensource.com][8] 写的文章在别处发表,有的是论文的参考文献,白皮书或者参考资料中。
###知识共享所代表的自由文化也是一种文化
### 知识共享所代表的自由文化也是一种文化
“自由文化”这个说法过于累赘,文化,从概念上来说,是一个有机的整体。在这种文化中社会逐渐成长发展,从一个人到另一个。它是人与人之间的互动和思想交流。自由文化是自由缺失的现代世界里的特殊产物。
如果你也想对这样的局限进行反抗,想把你的思想、作品你自己的文化分享给全世界的人,那么就来和我们一起,使用知识共享吧!
如果你也想对这样的局限进行反抗,想把你的思想、作品你自己的文化分享给全世界的人,那么就来和我们一起,使用知识共享吧!
--------------------------------------------------------------------------------
@ -44,7 +51,7 @@ via: https://opensource.com/article/18/1/creative-commons-real-world
作者:[Seth Kenlon][a]
译者:[Valoniakim](https://github.com/Valoniakim)
校对:[校对者ID](https://github.com/校对者ID)
校对:[wxy](https://github.com/wxy)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出

View File

@ -0,0 +1,472 @@
使用 gorilla/mux 进行 HTTP 请求路由和验证
======
> gorilla/mux 包以直观的 API 提供了 HTTP 请求路由、验证和其它服务。
![](https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/data_metrics_analytics_desktop_laptop.png?itok=9QXd7AUr)
Go 网络库包括 `http.ServeMux` 结构类型,它支持 HTTP 请求多路复用路由Web 服务器将托管资源的 HTTP 请求与诸如 `/sales4today` 之类的 URI 路由到代码处理程序;处理程序在发送 HTTP 响应(通常是 HTML 页面)之前执行适当的逻辑。 这是该体系的草图:
```
             +-----------+     +--------+     +---------+
HTTP 请求---->| web 服务器 |---->| 路由 |---->| 处理程序 |
             +-----------+     +--------+     +---------+
```
调用 `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("\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 请求的响应,依此类推。 这种简化使得使用命令行实用程序(如 [curl][1])可以轻松地测试应用程序,尤其是 `gorilla/mux` 组件。
`gorilla/mux` 包可以从 [GitHub][2] 安装。 CRUD app 无限期运行;因此,应使用 `Control-C` 或同等命令终止。 CRUD 应用程序的代码,以及自述文件和简单的 curl 测试,可以在[我的网站] [3]上找到。
### 2、请求路由
`mux.Router` 扩展了 REST 风格的路由,它赋给 HTTP 方法例如GET和 URL 末尾的 URI 或路径(例如 `/cliches`)相同的权重。 URI 用作 HTTP 动词(方法)的名词。 例如在HTTP请求中有一个起始行例如
```
GET /cliches
```
意味着得到所有的套话对,而一个起始线,如:
```
POST /cliches
```
意味着从 HTTP 正文中的数据创建一个套话对。
在 CRUD web 应用程序中,有五个函数充当 HTTP 请求的五种变体的请求处理程序:
```
ClichesAll(...)    # GET: 获取所有的套话对
ClichesOne(...)    # GET: 获取指定的套话对
ClichesCreate(...) # POST: 创建新的套话对
ClichesEdit(...)   # PUT: 编辑现有的套话对
ClichesDelete(...) # DELETE: 删除指定的套话对
```
每个函数都有两个参数:一个 `http.ResponseWriter` 用于向请求者发送一个响应,一个指向 `http.Request` 的指针,该指针封装了底层 HTTP 请求的信息。 使用 `gorilla/mux` 包可以轻松地将这些请求处理程序注册到Web服务器并执行基于正则表达式的验证。
CRUD 应用程序中的 `startServer` 函数注册请求处理程序。 考虑这对注册,`router` 作为 `mux.Router` 实例:
```
router.HandleFunc("/", ClichesAll).Methods("GET")
router.HandleFunc("/cliches", ClichesAll).Methods("GET")
```
这些语句意味着对单斜线 `/``/cliches` 的 GET 请求应该路由到 `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")
```
除了动词和 URI 之外,还可以在功能上路由 HTTP 请求。 例如,注册
```
router.HandleFunc("/cliches", ClichesCreate).Schemes("https").Methods("POST")
```
要求对 POST 请求进行 HTTPS 访问以创建新的套话对。以类似的方式,注册可能需要具有指定的 HTTP 头元素(例如,认证凭证)的请求。
### 3、 Request validation
`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` 路由器作为单独的 Go 协程执行对已注册的请求处理程序的每次调用,这意味着并发性被内置于包中。 例如,如果有十个同时发出的请求,例如
```
% curl --request POST --data "..." localhost:8888/cliches
```
然后 `mux.Router` 启动十个 Go 协程来执行 `ClichesCreate` 处理程序。
GET all、GET one、POST、PUT 和 DELETE 中的五个请求操作中,最后三个改变了所请求的资源,即包含套话对的共享 `clichesList`。 因此CRUD app 需要通过协调对 `clichesList` 的访问来保证安全的并发性。 在不同但等效的术语中CRUD app 必须防止 `clichesList` 上的竞争条件。 在生产环境中,可以使用数据库系统来存储诸如 `clichesList` 之类的资源,然后可以通过数据库事务来管理安全并发。
CRUD 应用程序采用推荐的Go方法来实现安全并发
* 只有一个 Go 协程,资源管理器在 CRUD app `startServer` 函数中启动,一旦 Web 服务器开始侦听请求,就可以访问 `clichesList`
* 诸如 `ClichesCreate``ClichesAll` 之类的请求处理程序向 Go 通道发送(指向)`crudRequest` 实例(默认情况下是线程安全的),并且资源管理器单独从该通道读取。 然后,资源管理器对 `clichesList` 执行请求的操作。
安全并发体系结构绘制如下:
```
           crudRequest                读/写
请求处理程序 -------------> 资源托管者 ------------> 套话列表
```
在这种架构中,不需要显式锁定 `clichesList`,因为一旦 CRUD 请求开始进入,只有一个 Go 协程(资源管理器)访问 `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` 函数创建一个新的 `ClichePair`,设置两个字段,并创建一个 `crudRequest` 发送给单个资源管理器。 此请求包括一个确认通道,资源管理器使用该通道将信息返回给请求处理程序。 所有设置工作都可以在不涉及资源管理器的情况下完成,因为尚未访问 `clichesList`
请求处理程序调用实用程序函数,该函数从 POST 请求中提取新的套话和反套话。 然后,该函数创建一个新的,设置两个字段,并创建一个 crudRequest 发送到单个资源管理器。 此请求包括一个确认通道,资源管理器使用该通道将信息返回给请求处理程序。 所有设置工作都可以在不涉及资源管理器的情况下完成,因为尚未访问它。
`completeRequest` 实用程序函数在 `ClichesCreate` 函数和其他请求处理程序的末尾调用:
```
completeRequest(cr, res, "create") // shown above
```
通过将 `crudRequest` 放入 `crudRequests` 频道,使资源管理器发挥作用:
```
func completeRequest(cr *crudRequest, res http.ResponseWriter, logMsg string) {
   crudRequests<-cr          // 向资源托管者发送请求
   msg := <-cr.confirm       // 等待确认
   res.Write([]byte(msg))    // 向请求方发送确认
   logIt(logMsg)             // 打印到标准输出
}
```
对于 POST 请求,资源管理器调用实用程序函数 `addPair`,它会更改 `clichesList` 资源:
```
func addPair(cp *clichePair) string {
   cp.Id = masterId  // 分配一个唯一的 ID
   masterId++        // 更新 ID 计数器
   clichesList = append(clichesList, cp) // 更新列表
   return "\nCreated: " + cp.Cliche + " " + cp.Counter + "\n"
}
```
资源管理器为其他 CRUD 操作调用类似的实用程序函数。 值得重复的是,一旦 Web 服务器开始接受请求,资源管理器就是唯一可以读取或写入 `clichesList` 的 goroutine。
对于任何类型的 Web 应用程序,`gorilla/mux` 包在简单直观的 API 中提供请求路由、请求验证和相关服务。 CRUD web 应用程序突出了软件包的主要功能。
--------------------------------------------------------------------------------
via: https://opensource.com/article/18/8/http-request-routing-validation-gorillamux
作者:[Marty Kalin][a]
选题:[lujun9972](https://github.com/lujun9972)
译者:[yongshouzhang](https://github.com/yongshouzhang)
校对:[wxy](https://github.com/wxy)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]:https://opensource.com/users/mkalindepauledu
[1]:https://curl.haxx.se/
[2]:https://github.com/gorilla/mux
[3]:http://condor.depaul.edu/mkalin

View File

@ -1,11 +1,11 @@
[#]: collector: (lujun9972)
[#]: translator: (geekpi)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: reviewer: (wxy)
[#]: publisher: (wxy)
[#]: subject: (Create a containerized machine learning model)
[#]: via: (https://fedoramagazine.org/create-containerized-machine-learning-model/)
[#]: author: (Sven Bösiger)
[#]: url: ( )
[#]: url: (https://linux.cn/article-10349-1.html)
创建一个容器化的机器学习模型
======
@ -32,7 +32,7 @@ mkdir deployment_container && cd deployment_container
下一步是为机器学习模型创建 REST API。这个 [github 仓库][4]包含一个预训练模型,以及能让 REST API 工作的设置。
使用以下命令在 deployment_container 目录中克隆它:
使用以下命令在 `deployment_container` 目录中克隆它:
```
git clone https://github.com/svenboesiger/titanic_tf_ml_model.git
@ -44,7 +44,7 @@ git clone https://github.com/svenboesiger/titanic_tf_ml_model.git
#### swagger.yaml
swagger.yaml 使用 [Swagger规范][7] 定义 Connexion 库的 API。此文件包含让你的服务器提供输入参数验证、输出响应数据验证、URL 端点定义所需的所有信息。
[swagger.yaml][12] 使用 [Swagger规范][7] 定义 Connexion 库的 API。此文件包含让你的服务器提供输入参数验证、输出响应数据验证、URL 端点定义所需的所有信息。
额外地Connexion 还将给你提供一个简单但有用的单页 Web 应用,它演示了如何使用 Javascript 调用 API 和更新 DOM。
@ -110,7 +110,7 @@ pandas
### 容器化!
为了让 Podman 构建映像,请在上面的准备步骤中创建的 **deployment_container** 目录中创建一个名为 “Dockerfile” 的新文件:
为了让 Podman 构建映像,请在上面的准备步骤中创建的 `deployment_container` 目录中创建一个名为 `Dockerfile` 的新文件:
```
FROM fedora:28
@ -171,7 +171,7 @@ via: https://fedoramagazine.org/create-containerized-machine-learning-model/
作者:[Sven Bösiger][a]
选题:[lujun9972][b]
译者:[geekpi](https://github.com/geekpi)
校对:[校对者ID](https://github.com/校对者ID)
校对:[wxy](https://github.com/wxy)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
@ -187,4 +187,5 @@ via: https://fedoramagazine.org/create-containerized-machine-learning-model/
[8]: https://github.com/svenboesiger/titanic_tf_ml_model/blob/master/server.py
[9]: https://github.com/svenboesiger/titanic_tf_ml_model/blob/master/requirements.txt
[10]: http://0.0.0.0:5000/
[11]: https://fedoramagazine.org/wp-content/uploads/2018/10/Screenshot-from-2018-10-27-14-46-56-682x1024.png
[11]: https://fedoramagazine.org/wp-content/uploads/2018/10/Screenshot-from-2018-10-27-14-46-56-682x1024.png
[12]: https://github.com/svenboesiger/titanic_tf_ml_model/blob/master/swagger.yaml

View File

@ -1,680 +0,0 @@
使用 gorilla/mux 进行HTTP请求路由和验证
======
![](https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/data_metrics_analytics_desktop_laptop.png?itok=9QXd7AUr)
Go 网络库包括 `http.ServeMux` 结构类型,它支持 HTTP 请求多路复用路由Web 服务器将托管资源的 HTTP 请求与诸如 sales4today 之类的 URI 路由到代码处理程序; 处理程序在发送 HTTP 响应(通常是 HTML 页面)之前执行适当的逻辑。 这是该体系的草图:
```
                 +------------+     +--------+     +---------+
HTTP 请求---->| web 服务器 |---->| 路由 |---->| 处理程序|
                 +------------+     +--------+     +---------+
```
调用 `ListenAndServe` 方法后启动 HTTP 服务器
```
http.ListenAndServe(":8888", nil) // args: port & router
```
第二个参数 `nil` 意味着 `DefaultServeMux` 用于请求路由。
`gorilla/mux` 库包含 `mux.Router` 类型,可替代 `DefaultServeMux` 或自定义请求多路复用器。 在 `ListenAndServe` 调用中,`mux.Router` 实例将代替 `nil` 作为第二个参数。 下面的示例代码很好的说明了为什么 `mux.Router`如此吸引人:
### 1\. A sample crud web app
crud web 应用程序(见下文)支持四种 CRUD创建读取更新删除操作它们分别对应四种 HTTP 请求方法POSTGETPUT 和 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("\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 请求的响应,依此类推。 这种简化使得使用命令行实用程序(如 [curl] [1])可以轻松地测试应用程序,尤其是 `gorilla/mux` 组件。
`gorilla/mux` 包可以从 [GitHub] [2] 安装。 crud app 无限期运行; 因此,应使用 Control-C 或同等命令终止。 crud 应用程序的代码,以及自述文件和简单的 curl 测试,可以在[我的网站] [3]上找到。
### 2\. 请求路由
`mux.Router` 扩展了 REST 风格的路由,它赋给 HTTP 方法例如GET和 URL 末尾的 URI 或路径(例如/cliches相同的权重。 URI 用作 HTTP 动词(方法)的名词。 例如在HTTP请求中有一个起始行例如
```
GET /cliches
```
意味着得到所有的陈词滥调对,而一个起始线,如
```
POST /cliches
```
意味着从HTTP正文中的数据创建一个陈词滥调对。
在 crud web 应用程序中有五个函数充当HTTP请求的五种变体的请求处理程序
```
ClichesAll(...)    # GET: 获取所有的陈词滥调对
ClichesOne(...)    # GET: 获取指定的陈词滥调对
ClichesCreate(...) # POST: 创建新的陈词滥调对
ClichesEdit(...)   # PUT: 编辑现有的陈词滥调对
ClichesDelete(...) # DELETE: 删除指定的陈词滥调对
```
每个函数都有两个参数:一个 `http.ResponseWriter` 用于向请求者发送一个响应,一个指向 `http.Request` 的指针,该指针封装了底层 HTTP 请求的信息。 使用 `gorilla/mux` 包可以轻松地将这些请求处理程序注册到Web服务器并执行基于正则表达式的验证。
crud 应用程序中的 `startServer` 函数注册请求处理程序。 考虑这对注册,`router` 作为 `mux.Router` 实例:
```
router.HandleFunc("/", ClichesAll).Methods("GET")
router.HandleFunc("/cliches", ClichesAll).Methods("GET")
```
这些语句意味着对单斜线/ 或 /cliches 的 GET 请求应该路由到 `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")
```
除了动词和 URI 之外,还可以在功能上路由 HTTP 请求。 例如,注册
```
router.HandleFunc("/cliches", ClichesCreate).Schemes("https").Methods("POST")
```
要求对 POST 请求进行 HTTPS 访问以创建新的陈词滥调对。 以类似的方式,注册可能需要具有指定的 HTTP 头元素(例如,认证凭证)的请求。
### 3\. Request validation
`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` 启动十个 goroutines 来执行 `ClichesCreate` 处理程序。
GET allGET onePOSTPUT 和 DELETE 中的五个请求操作中,最后三个改变了所请求的资源,即包含陈词滥调对的共享 `clichesList`。 因此crudapp 需要通过协调对`clichesList` 的访问来保证安全的并发性。 在不同但等效的术语中crud app 必须防止 `clichesList` 上的竞争条件。 在生产环境中,可以使用数据库系统来存储诸如 `clichesList` 之类的资源,然后可以通过数据库事务来管理安全并发。
crud 应用程序采用推荐的Go方法来实现安全并发
* 只有一个 goroutine资源管理器在 crud app`startServer` 函数中启动,一旦 Web 服务器开始侦听请求,就可以访问 `clichesList`
* 诸如 `ClichesCreate``ClichesAll` 之类的请求处理程序向 Go 通道发送(指向)`crudRequest` 实例(默认情况下是线程安全的),并且资源管理器单独从该通道读取。 然后,资源管理器对 `clichesList` 执行请求的操作。
安全并发体系结构绘制如下:
```
                 crudRequest                   读/写
请求处理程序 -------------> 资源托管者 ------------> 陈词滥调列表
```
在这种架构中,不需要显式锁定 `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")
}ClichesCreateres httpResponseWriterreqclichecountergetDataFromRequestreqcpclichePaircpClicheclichecpCountercountercr&crudRequestverbPOSTcpcpconfirmcompleteRequestcrres
```
`ClichesCreate` 调用实用函数 `getDataFromRequest`,它从 POST 请求中提取新的陈词滥调和反陈词滥调。 然后 `ClichesCreate` 函数创建一个新的 `ClichePair`,设置两个字段,并创建一个 `crudRequest` 发送给单个资源管理器。 此请求包括一个确认通道,资源管理器使用该通道将信息返回给请求处理程序。 所有设置工作都可以在不涉及资源管理器的情况下完成,因为尚未访问 `clichesList`
请求处理程序调用实用程序函数,该函数从 POST 请求中提取新的陈词滥调和反陈词滥调。 然后,该函数创建一个新的,设置两个字段,并创建 ato 发送到单个资源管理器。 此请求包括一个确认通道,资源管理器使用该通道将信息返回给请求处理程序。 所有设置工作都可以在不涉及资源管理器的情况下完成,因为尚未访问它。
`completeRequest` 实用程序函数在 `ClichesCreate` 函数和其他请求处理程序的末尾调用
```
completeRequest(cr, res, "create") // shown above
```
通过将 `crudRequest` 放入 `crudRequests` 频道,使资源管理器发挥作用:
```
func completeRequest(cr *crudRequest, res http.ResponseWriter, logMsg string) {
   crudRequests<-cr          // 向资源托管者发送请求
   msg := <-cr.confirm       // 等待确认
   res.Write([]byte(msg))    // 向请求方发送确认
   logIt(logMsg)             // 打印到标准输出
}
```
对于 POST 请求,资源管理器调用实用程序函数 `addPair`,它会更改 `clichesList` 资源:
```
func addPair(cp *clichePair) string {
   cp.Id = masterId  // 分配一个唯一的 ID
   masterId++        // 更新 ID 计数器
   clichesList = append(clichesList, cp) // 更新列表
   return "\nCreated: " + cp.Cliche + " " + cp.Counter + "\n"
}
```
资源管理器为其他CRUD操作调用类似的实用程序函数。 值得重复的是,一旦 Web 服务器开始接受请求,资源管理器就是唯一可以读取或写入 `clichesList` 的 goroutine。
对于任何类型的 Web 应用程序,`gorilla/mux` 包在简单直观的API中提供请求路由请求验证和相关服务。 crud web 应用程序突出了软件包的主要功能。 给包裹一个测试驱动,你可能会成为买主。
--------------------------------------------------------------------------------
via: https://opensource.com/article/18/8/http-request-routing-validation-gorillamux
作者:[Marty Kalin][a]
选题:[lujun9972](https://github.com/lujun9972)
译者:[yongshouzhang](https://github.com/yongshouzhang)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]:https://opensource.com/users/mkalindepauledu
[1]:https://curl.haxx.se/
[2]:https://github.com/gorilla/mux
[3]:http://condor.depaul.edu/mkalin