Go语言编程笔记17:Web Service
通过一系列文章,我介绍了如何用Go语言构建一个Web应用,准确的说是一个网站。事实上并非所有的Web应用都是以网站的形式存在,其中相当一部分是Web Service,相比前者,后者的应用范围更广泛,它的前端可能是纯Js编写的网站前端,也可能是移动APP,甚至是另一个Web应用。
所以这篇文章将介绍如何构建一个Web Service。
这里的Web Service概念和Apache之类的有所不同,它指那些通过API方式提供服务的Web应用。
在说明Web Service之前,要先说明两种流行的文本传输格式:XML和JSON,事实上大部分Web Service都会使用这两者其中之一作为API的载体。
XML
来看一个典型的XML文本:
<?xml version="1.0" encoding="UTF-8"?>
<article id="1" uid="1">
<Content>this is a art's content.</Content>
<comments>
<comment id="1" uid="1">first comment content.</comment>
<comment id="2" uid="1">second comment content.</comment>
<comment id="3" uid="2">third comment content.</comment>
</comments>
</article>
Content标签中的乱码是对
'
符号的转义。
解析
XML本身并不复杂,和HTML类似,都是由一系列标签组成。关于XML的相关定义这里不过多解释,直接看如何用Go语言解析:
package main
import (
"encoding/xml"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
)
type Article struct {
XMLName xml.Name `xml:"article"`
Id int `xml:"id,attr"`
Content string `xml:content`
Comments []Comment `xml:"comments>comment"`
Uid int `xml:"uid,attr"`
}
func (a *Article) String() string {
var comments []string
for _, c := range a.Comments {
comments = append(comments, c.String())
}
scs := strings.Join(comments, ",")
return fmt.Sprintf("Article(Id:%d,Content:'%s',Comments:[%s],Uid:%d)", a.Id, a.Content, scs, a.Uid)
}
type Comment struct {
XMLName xml.Name `xml:"comment"`
Id int `xml:"id,attr"`
Content string `xml:",chardata"`
Uid int `xml:"uid,attr"`
}
func (c *Comment) String() string {
return fmt.Sprintf("Comment(Id:%d,Content:'%s',Uid:%d)", c.Id, c.Content, c.Uid)
}
func main() {
fopen, err := os.Open("art.xml")
if err != nil {
panic(err)
}
defer fopen.Close()
content, err := ioutil.ReadAll(fopen)
if err != nil && err != io.EOF {
panic(err)
}
art := Article{
}
err = xml.Unmarshal(content, &art)
if err != nil {
panic(err)
}
fmt.Println(art.String())
}
Go用于处理XML格式的包为encoding/xml
。
和在Go语言编程笔记16:存储数据 - 魔芋红茶’s blog (icexmoon.xyz)中介绍的ORM类似,要将一个XML文本解析到Go的结构体,需要建立从XML到结构体的映射关系,这种映射关系同样体现为结构体的字段标签。
所谓的“字段标签”实际上就是结构体字段后用特殊单引号标注的部分。
字段标签有这么几种:
xml:"<tag_name>"
,指代当前标签中的名称为tag_name
的子标签的值。xml:"<attr_name>,attr"
,指代当前标签的名称为attr_name
的属性。xml:",innerxml"
,指代当前标签包含的XML文本。xml:",chardata"
,指代当前标签的值。xml:"a>b>c"
,指代当前标签下包含的a
标签包含的b
标签包含的c
标签,使用此标注可以直接让字段跨越几个层级对应到某个或某几个字标签。
比较特别的是,一般情况下解析器会使用结构的名称来匹配标签,比如用结构体Article
匹配<article>
标签,但如果结构体名称和标签不一致,就需要通过给结构体添加一个额外字段XMLName
来指定对应的标签:
type Article struct {
XMLName xml.Name `xml:"article"`
...
}
XMLName
的类型是xml.Name
,字段标签是xml:"<tag_name>"
。
通过字段标签构建好映射关系后就简单了,从文件或者数据流读取数据到字符串或者字节序列,然后调用xml.Unmarshal
函数进行解码即可。
大多数情况下用这种方式解析XML文本都是没有问题的,但有时候对于某些内容巨大的XML文件或者字节流,这样处理就不合适了,可能完整读取XML到内存都会是一项艰巨的任务。
所以xml
包还提供“逐句解析”的选项,这样做可以在节省内存的前提下解析大容量XML文本:
...
func main() {
fopen, err := os.Open("art.xml")
if err != nil {
panic(err)
}
defer fopen.Close()
d := xml.NewDecoder(fopen)
var comments []Comment
for {
token, err := d.Token()
if err == io.EOF {
//xml解析完毕
break
}
if err != nil {
//解析出错
panic(err)
}
switch node := token.(type) {
case xml.StartElement