HTML 中使用 golang 模板编程。
主要写两个案例,都是 HTML 参数解析展示类的模板编程。由于这部分内容比较简单,直接上代码了。
simple.go 解析 JSON 数据,展示到自定义的网页。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package mainimport ( "fmt" "html/template" "net/http" ) func main () { http.HandleFunc("/" , indexHandler) http.HandleFunc("/new/" , newsAggHandler) http.ListenAndServe(":8080" , nil ) } func indexHandler (w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "<h1> Hi, there.</h1" ) } type NewsPara struct { Title string News string } func newsAggHandler (w http.ResponseWriter, r *http.Request) { p := NewsPara{Title: "Wow" , News: "news" } t, _ := template.ParseFiles("simple.html" ) fmt.Println(t.Execute(w, p)) }
网页的模板代码非常简单: simple.html
然后控制台运行 localhost:8080/new/
看到参数已经解析到页面上了。(如果HTML 模板参数名字写错了,则报错)
下面来个稍微复杂一点的,复杂的模板使用。(从 WT xml 获取消息,然后显示到自己的网页)
模板如下: (网页上显示整个 map 的内容,所以需要循环)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <h1 > {{ .Title }}</h1 > <table > <thead > <th > Title</th > <th > KeyWords</th > </thead > <tbody > {{ range $key, $value := .News }} <tr > <td > <a href ="{{$value.Location}}" target ="_blank" > {{ $key }}</a > </td > <td > {{ $value.Keyword }}</td > </tr > {{ end }} </tbody > </table >
消息结构定义如下: (只是截取了标签内部分子标签)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type Sitemapindex struct { Locations []string `xml: "sitemap>loc"` } type News struct { Titles []string `xml:"url>news>title"` Keywords []string `xml:"url>news>keywords"` Locations []string `xml:"url>loc"` } type NewsMap struct { Keyword string Location string } type NewsPara struct { Title string News string }
然后摘取然后显示的代码如下, 其 xml 结构类似如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 This XML file does not appear to have any style information associated with it. The document tree is shown below. <sitemapindex xmlns ="http://www.sitemaps.org/schemas/sitemap/0.9" > <sitemap > <loc > http://www.washingtonpost.com/news-politics-sitemap.xml </loc > </sitemap > <sitemap > <loc > http://www.washingtonpost.com/news-blogs-politics-sitemap.xml </loc > </sitemap > </sitemapindex >
具体的每一条新闻类似:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <url > <loc > https://www.washingtonpost.com/politics/courts_law/is-california-protecting-women-or-forcing-clinics-to-promote-abortion-supreme-court-to-decide/2018/03/16/05ab6db4-2627-11e8-874b-d517e912f125_story.html </loc > <changefreq > hourly</changefreq > <n:news > <n:publication > <n:name > Washington Post</n:name > <n:language > en</n:language > </n:publication > <n:publication_date > 2018-03-17T00:30:17Z</n:publication_date > <n:title > Is California protecting women or forcing clinics to promote abortion? Supreme Court to decide. </n:title > <n:keywords > courts,informed choices,xavier becerra,supreme court,fake clinics,abortion </n:keywords > </n:news > </url >
如果你无法访问该网页,可以把代码改成请求本地的 XML 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 func newsHandler (w http.ResponseWriter, r *http.Request) { var s Sitemapindex var n News resp, err := http.Get("https://www.washingtonpost.com/news-sitemap-index.xml" ) if err != nil { log.Fatal(err) } bytes, err:= ioutil.ReadAll(resp.Body) if err != nil { log.Fatal(err) } resp.Body.Close() xml.Unmarshal(bytes, &s) if len (s.Locations)==0 { log.Fatal("get the news xml locations error" ) } newsMap := make (map [string ]NewsMap) for _, Location := range s.Locations { resp, _ := http.Get(Location) bytes, _ := ioutil.ReadAll(resp.Body) resp.Body.Close() xml.Unmarshal(bytes, &n) for idx := range n.Keywords { newsMap[n.Titles[idx]] = NewsMap{n.Keywords[idx], n.Locations[idx]} } } p := NewsPara{Title: "Wow" , News: newsMap} t, _ := template.ParseFiles("complex.html" ) fmt.Println(t.Execute(w, p)) }
为了让表格好看,可以加上一些格式代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 <head > <script type ="text/javascript" charset ="utf8" src ="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js" > </script > <script type ="text/javascript" charset ="utf8" src ="//cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js" > </script > <link rel ="stylesheet" type ="text/css" href ="//cdn.datatables.net/1.10.16/css/jquery.dataTables.min.css" > </head > <script > $(document ).ready( function ( ) { $('#myTable' ).DataTable(); } ); </script > <table id ="mytable" class ="display" > ... </table >
具体效果如下:
另外,可以看到网页的展示效果,但是就是速度上很慢,因为我们是顺序下载然后展示的,CPU 很大一部分时间在 idle 等待 IO 完毕(或者对方 server reponse 写回),其实可以并行执行,同时多个 routine 去取,然后先返回的显写入 map.
做法有很多:比如开 N 个 goroutines, 把获取 xml news content 的工作交出去(哪几个 routines 抢到工作我不关心),然后在 main routine 里面不断的读取 chan (这个 chan 可以开 x buffer,避免阻塞),然后写入 map,即写入newsMap; 并且只能主 routine 写。
具体的模型可以参考这篇我写的这篇文章的线程池部分: 《Golang: Here We Go(5.孰能生巧)》
读取 xml news 可以有多个,但是写保证只有一个人,即 main 在写(避免混乱)
main 和其他 goroutines 之间可以用 sync.WaitGroup 的 Done() 和 Add(),也可以不用,读写同一个 阻塞型 chan 自动保证了协作&同步。
我给出另外一种方案,使用 WaitGroup 的方案:(队列 + WaitGroup保证主routine等待其他routine完成具体的工作)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 var wg sync.WaitGroupfunc newsHandler (w http.ResponseWriter, r *http.Request) { var s Sitemapindex resp, err := http.Get("https://www.washingtonpost.com/news-sitemap-index.xml" ) if err != nil { log.Fatal(err) } bytes, err:= ioutil.ReadAll(resp.Body) if err != nil { log.Fatal(err) } resp.Body.Close() xml.Unmarshal(bytes, &s) if len (s.Locations)==0 { log.Fatal("get the news xml locations error" ) } queue := make (chan News, 50 ) newsMap := make (map [string ]NewsMap) log.Println(len (s.Locations)) for _, Location := range s.Locations { wg.Add(1 ) go worker(queue, Location) } wg.Wait() close (queue) for elem := range queue { for idx := range elem.Keywords { newsMap[elem.Titles[idx]] = NewsMap{elem.Keywords[idx], elem.Locations[idx]} } } p := NewsPara{Title: "Wow" , News: newsMap} t, _ := template.ParseFiles("complex.html" ) fmt.Println(t.Execute(w, p)) } func worker (newsChan chan News, Location string ) { defer wg.Done() var n News resp, _ := http.Get(Location) bytes, _ := ioutil.ReadAll(resp.Body) resp.Body.Close() xml.Unmarshal(bytes, &n) newsChan <- n }
速度提升了快了很多:
但是还能不能更快一点?应该可以,开更多的线程,比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 queue := make (chan News, 50 ) urlChan := make (chan string , 15 ) for i :=0 ; i<50 ; i++ { wg.Add(1 ) go worker(queue, urlChan) } for _, Location := range s.Locations { go dispathURL(urlChan, Location) }
但是这里同步控制就比较烦了,控制不当可能有的 routine 一直等待写 urlChan。(因为还没有close)
并发部分,要么非常懂再去写;懂一点点&知道语法,很容易写出带有Bug的并发程序。
Merlin 2018.3 Golang HTML 模板(根据本案例扩展,其实可以做一个 RSS 订阅器)