【go】《Go语言编程》第五章完整示例问题

第五章的示例 正是我想做的一个东西,所以我会慢慢将该示例丰富。而且该示例由浅入深,层层递进,写得很不错,特别适合学习。

还没有全部完成,就已经遇到了几个问题。希望可以解答。

  1. 在没有使用html模版之前,html相关的代码如果不添加<html></html>的标签,是不会解析html的。但是同样的代码不包含<html></html>标签,单独写在1.html文件里面用浏览器打开又是可以的,是因为go的html函数解析必需要html标签吗?
  2. 在listHandler方法里面,_,fileInfo := range fileInfoArr 获取到了文件的相关信息,但是使用imgid := fileInfo.Name 出现了一个问题,该如何使用。 method fileInfo.Name is not an expression, must be called
  3. 另外几个小问题都是印刷问题在viewHandler里面的imageId和imagePath 都没有定义 改成:=就可以了。
  4. 还有一个问题,因为这里省略了缩略图(不是本章的重点),而想实现该功能,自己在网上找一了个resize.go https://github.com/nfnt/resize 不知道这个怎么样,实际操作处理缩略图的速度很慢。还没有仔细研究,有没有比较成熟的代码和建议。

继上次提出的几个问题后:

  1. 在模版缓存这里开始,定义了一个templates,这个是全局的一个变量,定义在函数体之外,是不能使用:=的短格式,可能是copy的原因导致的,需要使用var定义。var templates = map[string]*template.Template{} 和var templates = make(map[string]*template.Template) 这两个的定义都是可以的 我想问的是他们有区别吗?
  2. 再有个问题是 templatesp[tmpl]=t 这里并没有tmpl这个变量 我想是截取代码时候造成的,但并没有测试代码,tmpl应该是upload 或者list,所以对templateName进行了split操作。
  3. 使用了check函数后,代码相应的变化,但有些地方忘了添加check有些地方err:= 不再需要:号 因为没有if所以没有了局部变量。
  4. safeHandler函数里面http.Error( w,err.Error(),http.StatusInternalServerError ) err要么改要么其它地方改 没有统一
  5. 在动静分离的时候定义了一个常量ListDir=0x001,这个十六进制的数与flags进行位运算没有太明白原理,书中因为简略的描述,没有太明白,能麻烦详细解释下吗?
  6. 在查看图片的时候 会打印 2012/10/26 17:54:14 http: StatusNotModified response with header "Content-Type" defined 是因为这个设置报出来的吗? w.Header().Set("Content-Type","image") ,另外image写得是否正确?
  7. 最后,整个示例的层层递进关系非常好,非常感谢。但示例最后好像因为截取copy等关系并没有严格的测试,还有一些格式等小问题,我将代码放置在了我的blog上面,有时间会整理所有章节的问题和示例代码进行打包发布。关于这章的http://www.ohlinux.com/archives/825/ 

这章的示例还在完善,做个小相册玩玩。

回答

Q1: html 标签渲染和 golang 函数没有关系,用 <html> 标签包裹HTML <body> 是 html 的语法要求。书中的示例代码是简写形式,用代码输出html body,目的是为了便于过渡理解后续模板使用。

考虑到读者不可能全是做过web开发碰过html的,这里略微讲下 html 基础。

纯粹用 <html> 标签包裹起来,不同的浏览器理解行为也不一样的。

严格意义上的HTML语法,在 <html> 标签之上,也即更外部一层,需要声明 Doctype tag,而 doctype 又分多种类型,比如:

XHTML DOCTYPES

doctype html

<!DOCTYPE html>

doctype 5

<!DOCTYPE html>

doctype 1.1

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"

"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

doctype strict

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

doctype frameset

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">

doctype mobile

<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN"

"http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">

doctype basic

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN"

"http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">

doctype transitional

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

HTML 4 DOCTYPES

doctype strict

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"

"http://www.w3.org/TR/html4/strict.dtd">

doctype frameset

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"

"http://www.w3.org/TR/html4/frameset.dtd">

doctype transitional

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"

"http://www.w3.org/TR/html4/loose.dtd">

针对 PC 端的桌面浏览器,现在用的最多的就是 doctype 1.1 标准

doctype 1.1

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" 

"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

加上现在 HTML5 这波热潮,doctype 5 用的也比较流行。

doctype 5

<!DOCTYPE html>

其中,doctype 1.1 标准最通用,各浏览器都能良好理解。但 doctype 5 只对支持HTML5特性的浏览器有较好的支持。

再回到正题,书里边的样例代码部分是示范性的,比如下面的 “代码片段1” 作为一个网页雏形,目的是为了便于过渡理解 “代码片段2”中的模板使用,如果楼主顺着章节看下去不难理解这是一种循序渐进的讲解。因为看到章节的末尾会有完整的工程源代码,也有标准的HTML模板文件,最终实现了一个精简的MVC 模型,可以像个正常的网站程序跑起来。

// 代码片段1

func uploadHandler(w http.ResponseWriter, r *http.Request) {

if r.Method == "GET" {

io.WriteString(w, "<form method=\"POST\" action=\"/upload\" "+

" enctype=\"multipart/form-data\">"+

"Choose an image to upload: <input name=\"image\" type=\"file\" />"+

"<input type=\"submit\" value=\"Upload\" />"+

"</form>")

return

}

// ...

}

// 代码片段2

func uploadHandler(w http.ResponseWriter, r *http.Request) {

if r.Method == "GET" {

t, err := template.ParseFiles("upload.html")

if err != nil {

http.Error(w, err.Error(), http.StatusInternalServerError)

return

}

t.Execute(w, nil)

return

}

// ...

}

// 按照楼主要求,加上 html 标签,代码片段1应该如下写,浏览器可以渲染成网页

func uploadHandler(w http.ResponseWriter, r *http.Request) {

if r.Method == "GET" {

io.WriteString(w, "<!DOCTYPE html><html><body>"+

"<form method=\"POST\" action=\"/upload\" "+

" enctype=\"multipart/form-data\">"+

"Choose an image to upload: <input name=\"image\" type=\"file\" />"+

"<input type=\"submit\" value=\"Upload\" />"+

"</form>"+

"</body></html>")

return

}

// ...

}

《Go语言编程》是一本不厚很简洁的书,本身是因为Go简洁高效的表达,书中的内容更多的是展现Go语言如何编程及其实践使用。“用Go语言开发网站” 只是书中[网络编程]这章的一小节内容,以简短的篇幅展现了用原生的Go语言而不是借助任何第三方框架或者库来开发一个网站的具体细节,相信有过web开发基础的朋友不难理解。不过作者没有把普及 HTML 基础知识写在本书内。

Q2 and Q3: 不确定是不是排版印刷问题,我这里的原稿源代码显示如下,请自行更正:

Q2 代码片段

func listHandler(w http.ResponseWriter, r *http.Request) {

fileInfoArr, err := ioutil.ReadDir("./uploads")

check(err)

locals := make(map[string]interface{})

images := []string{}

for _, fileInfo := range fileInfoArr {

images = append(images, fileInfo.Name())

}

locals["images"] = images

renderHtml(w, "list", locals)

}

Q3 代码片段

func viewHandler(w http.ResponseWriter, r *http.Request) {

imageId := r.FormValue("id")

imagePath := UPLOAD_DIR + "/" + imageId

if ok := isExists(imagePath); !ok {

http.NotFound(w, r)

return

}

w.Header().Set("Content-Type", "image")

http.ServeFile(w, r, imagePath)

}

书中的代码错误我们会和出版社联系,新的印刷中避免出现。

Q4: Go语言关于处理图像的原生库目前不太成熟,我们也试验过Go的图像处理最后放弃,生产环境中没有使用。成熟的图像处理工具可以参考 ImageMagick 或者 GraphicsMagick,也可以使用 七牛云存储提供的在线图像处理接口

以下内容作为楼主修改问题后的补充回答。

1.在模版缓存这里开始,定义了一个templates,这个是全局的一个变量,定义在函数体之外,是不能使用:=的短格式,可能是copy的原因导致的,需要使用var定义。var templates = map[string]*template.Template{} 和var templates = make(map[string]*template.Template) 这两个的定义都是可以的 我想问的是他们有区别吗?

templates 变量就是全局的。并非局部定义,为确定我心中的疑惑,我刚才看了下我们这边的电子书稿,是使用如下方式声明的。

var templates = make(map[string]*template.Template)

然后,我自己看书翻阅,发现代码确实是使用 := 形式声明在函数体之外的,囧。我表示找不出合适理由解释,但可以承诺会和出版社沟通书中存在样例代码错误的问题。楼主可以私信给我你的收货地址,更新后的书我买本寄送给你。

以下两段代码是等价的。

var templates = make(map[string]*template.Template)

var templates = map[string]*template.Template{}

两者的共同之处在于:返回“该map结构”的初始化值。

不同之处在于:make() 可用于创建 slices, maps, channels。比如创建 slice 时,完整的语法规格如下:

s := make([]<Type>, len, cap)

关于 make() 的细节,可以查阅书中相应的基础章节。

2.再有个问题是 templatesp[tmpl]=t 这里并没有tmpl这个变量 我想是截取代码时候造成的,但并没有测试代码,tmpl应该是upload 或者list,所以对templateName进行了split操作。

为了确认书中是否存在印刷错误,我又看了遍书。tmpl 这个变量我在书中(P150, P151)明明有看到,代码摘抄如下:

P150

func init() {

for _, tmpl := range []string{"upload", "list"} {

t := template.Must(template.ParseFiles(tmpl + ".html"))

templates[tmpl] = t

}

}

如上这段代码中的 tmpl 来自 for 循环 range 遍历 string 数组得的的元素。

range 是 Go 语言中的保留关键字,可用于循环。range 可以针对 slice、array、string、map 和 channel 类型的数据结构进行迭代操作。基于不同的内容,range 返回不同的东西,基本来讲,会从它循环的内容中返回一个 key/value 。如上代码中,当对 []string 数组(array)做循环时,range 返回序号作为 key(我们用匿名变量下划线_作丢弃处理),而这个序号所对应的内容(value)作为值赋值给了 tmpl

关于 range 保留关键字的更多细节,可以查阅书中的基础章节。

P151

func renderHtml(w http.ResponseWriter, tmpl string, locals map[string]interface{}) (err error){

tmpl += ".html"

err = templates[tmpl].Execute(w, locals)

}

这段代码中的 tmpl 也不是不存在,可以明显看到是从函数 renderHtml() 作为参数传入。

3.使用了check函数后,代码相应的变化,但有些地方忘了添加check有些地方err:= 不再需要:号 因为没有if所以没有了局部变量。

章节前半部分由于展示关键代码部分可能会少写代码片段,看了下书中章节中的最终代码 photoweb.go,凡是有 err 赋值的地方都紧跟着出现了 check(err) 检测。isExists() 函数中未出现 check(err) 是因为该函数 return os.IsExist(err)。另需要说明的是:check(err) 表达的是 “把程序错误终止在它该出现的地方” 这样一种思想。实际生产环境中,类似的错误处理可能需要更细致而不是那么笼统。

有些地方err:= 不再需要:号 因为没有if所以没有了局部变量。

没有看明白这句话的意思,因为没有上下文可以参考所以实在想不出来楼主要表达的意思。

一个 comma,ok 形式的语句,只要是有新变量赋值操作,那么必须用 :=,比如:

newVar, err := ...

而当 err 变量已经声明后,在没有新变量赋值操作的情况下,不必用 :=,比如书中样例代码的 uploadHandler() 函数,前两个 err 赋值语句都用了 := 操作,而第3个 err 由于没有产生新变量(_, err = ...)所有没有用 := 而是直接 = 赋值。摘抄代码如下:

func uploadHandler(w http.ResponseWriter, r *http.Request) {

if r.Method == "GET" {

renderHtml(w, "upload", nil)

}

if r.Method == "POST" {

f, h, err := r.FormFile("image")

check(err)

filename := h.Filename

defer f.Close()

t, err := os.Create(UPLOAD_DIR + "/" + filename)

check(err)

defer t.Close()

_, err = io.Copy(t, f)

check(err)

http.Redirect(w, r, "/view?id="+filename, http.StatusFound)

}

}

4.safeHandler函数里面http.Error( w,err.Error(),http.StatusInternalServerError ) err要么改要么其它地方改 没有统一

可能是楼主好心建议,不过没有上下文参考表示没有看明白。尽管没看明白,还是稍作简单理解,应该是章节前半部分的代码片段用到了这样的写法。建议以章节中最终的 photoweb.go 程序为完整示例代码参考。完整示例代码 photoweb.go 中,由于重构关系,只有一个地方也即 safeHandler() 函数中出现 http.Error(w, e.Error(), http.StatusInternalServerError)

5.在动静分离的时候定义了一个常量ListDir=0x001,这个十六进制的数与flags进行位运算没有太明白原理,书中因为简略的描述,没有太明白,能麻烦详细解释下吗?

看了下,书中的此处代码显得有些多余;又看了下电子稿,发现跟书上确实有不一致的地方,新版印刷书中会修复此问题。此处的位运算逻辑只是判断要不要列出目录,flags 为 0 即表示只handle目录中的文件而不列目录。正确的代码如下:

func staticDirHandler(mux *http.ServeMux, prefix string, staticDir string, flags int) {

mux.HandleFunc(prefix, func(w http.ResponseWriter, r *http.Request) {

file := staticDir + r.URL.Path[len(prefix)-1:]

if (flags & ListDir) == 0 {

fi, err := os.Stat(file)

if err != nil || fi.IsDir() {

http.NotFound(w, r)

return

}

}

http.ServeFile(w, r, file)

})

}

6.在查看图片的时候 会打印 2012/10/26 17:54:14 http: StatusNotModified response with header "Content-Type" defined 是因为这个设置报出来的吗? w.Header().Set("Content-Type","image") ,另外image写得是否正确?

w.Header().Set("Content-Type","image") 是作者偷懒简写,但也是可以正常work的,你问题中报的日志跟这个简写没有关系。实际应用中应该输出文件具体的 MIME-Type 类型。比如 image/jpg, image/png 等等。MIME-Type 规格如下:

Content-Type: [type]/[subtype]; parameter

MIME根据type制定了默认的subtype,当客户端不能确定消息的subtype的情况下,消息被看作默认的subtype进行处理。所以,示例程序运行时浏览器可正常显示图片。

7.最后,整个示例的层层递进关系非常好,非常感谢。但示例最后好像因为截取copy等关系并没有严格的测试,还有一些格式等小问题,我将代码放置在了我的blog上面,有时间会整理所有章节的问题和示例代码进行打包发布。关于这章的http://www.ohlinux.com/archives/825/

非常感谢楼主的热心!:)

不过书中由于篇幅只是写了一个很小很小的web例子,便于读者们理解用原生的Go语言开发web程序所需了解的各个细节。楼主若有兴趣可以写个更实用型的完整web案例!

《Go语言编程》一书的源代码在 Github 上可以找到:https://github.com/qiniu/gobook

photoweb.go 的源代码在此:https://github.com/qiniu/gobook/blob/...

欢迎提交 issues 和 pull requests!

另对书中出现的一些印刷问题,笔者表示非常抱歉,必赠楼主新书一本。

// 这是我最后调通的代码,仅供后来人参考
package main

import (

"html/template"

"io"

"io/ioutil"

"log"

"net/http"

"os"

"path"

)

const (

UPLOAD_DIR   = "./uploads"

TEMPLATE_DIR = "./templates"

)

var templates = make(map[string]*template.Template)

func init() {

fileInfoArr, err := ioutil.ReadDir(TEMPLATE_DIR)

if err != nil {

panic(err)

return

}

var templateName, templatePath string

for _, fileInfo := range fileInfoArr {

templateName = fileInfo.Name()

if ext := path.Ext(templateName); ext != ".html" {

continue

}

templatePath = TEMPLATE_DIR + "/" + templateName

log.Println("Loading template:", templatePath)

t := template.Must(template.ParseFiles(templatePath))

templates[templateName] = t

}

}
func uploadHandler(w http.ResponseWriter, r *http.Request) {

if r.Method == "GET" {

err := renderHtml(w, "upload.html", nil)

if err != nil {

http.Error(w, err.Error(), http.StatusInternalServerError)

return

}

return

}

if r.Method == "POST" {

f, h, err := r.FormFile("image")

if err != nil {

http.Error(w, err.Error(), http.StatusInternalServerError)

return

}

filename := h.Filename

defer f.Close()

t, err := os.Create(UPLOAD_DIR + "/" + filename)

if err != nil {

http.Error(w, err.Error(),

http.StatusInternalServerError)

return

}

defer t.Close()

if _, err := io.Copy(t, f); err != nil {

http.Error(w, err.Error(), http.StatusInternalServerError)

return

}

http.Redirect(w, r, "/view?id="+filename, http.StatusFound)

}

}

func viewHandler(w http.ResponseWriter, r *http.Request) {

imageId := r.FormValue("id")

imagePath := UPLOAD_DIR + "/" + imageId

if exists := isExists(imagePath); !exists {

http.NotFound(w, r)

return

}

w.Header().Set("content-Type", "image")

http.ServeFile(w, r, imagePath)

}

func listHandler(w http.ResponseWriter, r *http.Request) {

fileInfoArr, err := ioutil.ReadDir("./uploads")

if err != nil {

http.Error(w, err.Error(), http.StatusInternalServerError)

return

}

locals := make(map[string]interface{})

images := []string{}

for _, fileInfo := range fileInfoArr {

images = append(images, fileInfo.Name())

}

locals["images"] = images

err = renderHtml(w, "list.html", locals)

if err != nil {

http.Error(w, err.Error(), http.StatusInternalServerError)

return

}

}

func renderHtml(w http.ResponseWriter, tmpl string, locals map[string]interface{}) (err error) {

err = templates[tmpl].Execute(w, locals)

return

}

func isExists(path string) bool {

_, err := os.Stat(path)

if err == nil {

return true

}

return os.IsExist(err)

}

func main() {

http.HandleFunc("/list", listHandler)

http.HandleFunc("/view", viewHandler)

http.HandleFunc("/upload", uploadHandler)

err := http.ListenAndServe(":8080", nil)

if err != nil {

log.Fatal("ListenAndServer:", err.Error())

}

}

今天看这本书,遇到了这个问题,看了上面的回答,并没有直接解决我的疑问,“书中的错误到底在哪里?”
【go】《Go语言编程》第五章完整示例问题

因为用了IDE其实可以很明显看到图中的错误,但是由于第一次解除GO,以及以前接触过其他编程语言,总以为 fileInfo.Name 是得到这个对象的值,其实错误很简单,Name fileInfo的一个方法,而不是属性,加上括号就解决了。

下面是该段代码在该小节,我调通的版本

func listHandler(w http.ResponseWriter, r *http.Request) {

fileInfoArr, err := ioutil.ReadDir("./uploads")

if err != nil {

http.Error(w, err.Error(),

http.StatusInternalServerError)

return

}

var listHtml string

for _, fileInfo := range fileInfoArr {

imgid := fileInfo.Name()

listHtml += "<li><a href=\"/view?id=" + imgid + "\">" + imgid + "</a></li>"

}

io.WriteString(w, "<html><ol>"+listHtml+"</ol></html>")

}

以上是 【go】《Go语言编程》第五章完整示例问题 的全部内容, 来源链接: utcz.com/a/98338.html

回到顶部