Unknown před 13 roky
rodič
revize
ffca9f3c2b
97 změnil soubory, kde provedl 7353 přidání a 57 odebrání
  1. 1 1
      README.md
  2. 40 56
      eBook/04.4.md
  3. 22 0
      eBook/04.5.md
  4. 1 0
      eBook/directory.md
  5. 1027 0
      eBook/examples/chapter_15/Codelab Writing Web Applications - The Go Programming Language.htm
  6. 205 0
      eBook/examples/chapter_15/Codelab Writing Web Applications - The Go Programming Language_files/all.css
  7. 47 0
      eBook/examples/chapter_15/Codelab Writing Web Applications - The Go Programming Language_files/ga.js
  8. 190 0
      eBook/examples/chapter_15/Codelab Writing Web Applications - The Go Programming Language_files/godocs.js
  9. 36 0
      eBook/examples/chapter_15/client.go
  10. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/00changelog.i
  11. 1 0
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/branch
  12. 2 0
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/cache/branchheads
  13. 2 0
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/cache/tags
  14. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/dirstate
  15. 2 0
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/hgrc
  16. 4 0
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/requires
  17. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/00changelog.i
  18. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/00manifest.i
  19. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/_a_u_t_h_o_r_s.i
  20. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/_c_o_n_t_r_i_b_u_t_o_r_s.i
  21. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/_l_i_c_e_n_s_e.i
  22. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/_r_e_a_d_m_e.i
  23. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/codereview.cfg.i
  24. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/dict/dict.go.i
  25. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/spdy/read.go.i
  26. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/spdy/spdy__test.go.i
  27. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/spdy/types.go.i
  28. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/spdy/write.go.i
  29. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/client.go.i
  30. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/hixie.go.i
  31. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/hixie__test.go.i
  32. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/hybi.go.i
  33. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/hybi__test.go.i
  34. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/server.go.i
  35. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/websocket.go.i
  36. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/websocket__test.go.i
  37. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/~2ehgignore.i
  38. 19 0
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/fncache
  39. binární
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/undo
  40. 0 0
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/undo.bookmarks
  41. 1 0
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/undo.branch
  42. 3 0
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/undo.desc
  43. 0 0
      eBook/examples/chapter_15/code.google.com/p/go.net/.hg/undo.dirstate
  44. 2 0
      eBook/examples/chapter_15/code.google.com/p/go.net/.hgignore
  45. 3 0
      eBook/examples/chapter_15/code.google.com/p/go.net/AUTHORS
  46. 3 0
      eBook/examples/chapter_15/code.google.com/p/go.net/CONTRIBUTORS
  47. 27 0
      eBook/examples/chapter_15/code.google.com/p/go.net/LICENSE
  48. 3 0
      eBook/examples/chapter_15/code.google.com/p/go.net/README
  49. 2 0
      eBook/examples/chapter_15/code.google.com/p/go.net/codereview.cfg
  50. 210 0
      eBook/examples/chapter_15/code.google.com/p/go.net/dict/dict.go
  51. 312 0
      eBook/examples/chapter_15/code.google.com/p/go.net/spdy/read.go
  52. 497 0
      eBook/examples/chapter_15/code.google.com/p/go.net/spdy/spdy_test.go
  53. 369 0
      eBook/examples/chapter_15/code.google.com/p/go.net/spdy/types.go
  54. 285 0
      eBook/examples/chapter_15/code.google.com/p/go.net/spdy/write.go
  55. 137 0
      eBook/examples/chapter_15/code.google.com/p/go.net/websocket/client.go
  56. 695 0
      eBook/examples/chapter_15/code.google.com/p/go.net/websocket/hixie.go
  57. 201 0
      eBook/examples/chapter_15/code.google.com/p/go.net/websocket/hixie_test.go
  58. 549 0
      eBook/examples/chapter_15/code.google.com/p/go.net/websocket/hybi.go
  59. 584 0
      eBook/examples/chapter_15/code.google.com/p/go.net/websocket/hybi_test.go
  60. 102 0
      eBook/examples/chapter_15/code.google.com/p/go.net/websocket/server.go
  61. 412 0
      eBook/examples/chapter_15/code.google.com/p/go.net/websocket/websocket.go
  62. 274 0
      eBook/examples/chapter_15/code.google.com/p/go.net/websocket/websocket_test.go
  63. 29 0
      eBook/examples/chapter_15/dial.go
  64. 146 0
      eBook/examples/chapter_15/elaborated_webserver.go
  65. 24 0
      eBook/examples/chapter_15/hello_world_webserver.go
  66. 23 0
      eBook/examples/chapter_15/http_fetch.go
  67. 17 0
      eBook/examples/chapter_15/pipeline1.go
  68. 30 0
      eBook/examples/chapter_15/poll_url.go
  69. 14 0
      eBook/examples/chapter_15/predefined_functions.go
  70. 57 0
      eBook/examples/chapter_15/robust_webserver.go
  71. 39 0
      eBook/examples/chapter_15/rpc_client.go
  72. 13 0
      eBook/examples/chapter_15/rpc_objects.go
  73. 32 0
      eBook/examples/chapter_15/rpc_server.go
  74. 37 0
      eBook/examples/chapter_15/server.go
  75. 86 0
      eBook/examples/chapter_15/simple_tcp_server.go
  76. 42 0
      eBook/examples/chapter_15/simple_webserver.go
  77. 30 0
      eBook/examples/chapter_15/smtp.go
  78. 29 0
      eBook/examples/chapter_15/smtp_auth.go
  79. 32 0
      eBook/examples/chapter_15/socket.go
  80. 23 0
      eBook/examples/chapter_15/template_field.go
  81. 26 0
      eBook/examples/chapter_15/template_ifelse.go
  82. 22 0
      eBook/examples/chapter_15/template_validation.go
  83. 24 0
      eBook/examples/chapter_15/template_variables.go
  84. 20 0
      eBook/examples/chapter_15/template_with_end.go
  85. 42 0
      eBook/examples/chapter_15/twitter_status.go
  86. 29 0
      eBook/examples/chapter_15/websocket_client.go
  87. 29 0
      eBook/examples/chapter_15/websocket_server.go
  88. 2 0
      eBook/examples/chapter_15/wiki/ANewPage.txt
  89. 1 0
      eBook/examples/chapter_15/wiki/TestPage.txt
  90. 6 0
      eBook/examples/chapter_15/wiki/edit.html
  91. 2 0
      eBook/examples/chapter_15/wiki/page.txt
  92. 1 0
      eBook/examples/chapter_15/wiki/page1.txt
  93. 3 0
      eBook/examples/chapter_15/wiki/page5.txt
  94. 5 0
      eBook/examples/chapter_15/wiki/view.html
  95. 97 0
      eBook/examples/chapter_15/wiki/wiki.go
  96. 32 0
      eBook/examples/chapter_15/wiki/wiki_part1.go
  97. 39 0
      eBook/examples/chapter_15/wiki/wiki_part2.go

+ 1 - 1
README.md

@@ -8,7 +8,7 @@
 该翻译版本已获得原作者(Ivo Balbaert)本人授权,并表示支持开源事业的发展!
 
 ##翻译进度
-4.3 [常量](eBook/04.3.md)
+4.4 [变量](eBook/04.4.md)
 
 ##支持本书
 如果你喜欢本书《Go入门指南》,你可以参与到本书的翻译或纠正工作中来,具体请联系【无闻 E-mail:joe2010xtmf#163.com】,一同完善本书并帮助壮大 Go 语言在国内的学习群体,给大家提供更好的学习资源。

+ 40 - 56
eBook/04.4.md

@@ -1,17 +1,3 @@
-##啊哦,亲,你看得也太快了。。。还没翻译完呢 0 0
-要不等到 ***2013 年 4 月 25 日*** 再来看看吧~~
-
-这里还有一些其它的学习资源噢~
-
- - [《Go编程基础》](https://github.com/Unknwon/go-fundamental-programming):已更新至 [第9课](https://github.com/Unknwon/go-fundamental-programming/blob/master/lecture9/lecture9.md) 
- - [《Go Web编程》](https://github.com/astaxie/build-web-application-with-golang)
-
-神马?你说你不想学习?那好吧,去逛逛论坛看看行情也行~
-
-- [Golang中文社区](http://bbs.mygolang.com/forum.php)
-- [Go语言学习园地](http://studygolang.com/)
-- [Golang中国](http://golang.tc)
-
 #4.4 变量
 ##4.4.1 简介
 声明变量的一般形式是使用 `var` 关键字:`var identifier type`。
@@ -154,78 +140,77 @@ Example 4.5 [goos.go](examples/chapter_4/goos.go)
 
 在 Go 语言中,指针(第 4.9 节)属于引用类型,其它的引用类型还包括 slices(第 7 章),maps(第 8 章)和 channel(第 13 章)。被引用的变量会存储在堆中,以便进行垃圾回收,且比栈拥有更大的内存空间。
 
-## 4.4.3 打印CHECK
-
-函数 Printf 在 fmt 包外也是可见的,因为它以大写字母 P 开头,主要用于打印输出到控制台。通常使用的格式化字符串作为第一个参数:
+## 4.4.3 打印
+函数 `Printf` 可以在 fmt 包外部使用,这是因为它以大写字母 P 开头,该函数主要用于打印输出到控制台。通常使用的格式化字符串作为第一个参数:
 
     func Printf(format string, list of variables to be printed)
 
-在 Listing 4.5 中,格式化字符串为: **"The operating system is: %s\n"**
+在 Example 4.5 中,格式化字符串为:`"The operating system is: %s\n"`。
 
-这个格式化字符串含有一个或更多的格式化标识符 `%..`, `..` 表示可以替换不同的值,如 **%s** 代表一个字符串值。**%v** 标识默认的格式化标识符。这些标识符的值从逗号之后顺序排列,如果有超过1个,它们之间用逗号分隔。这些 % 占位符的格式可以精细控制
+这个格式化字符串可以含有一个或多个的格式化标识符,例如:`%..`,其中 `..` 可以被不同类型所对应的标识符替换,如 `%s` 代表字符串标识符、`%v` 代表使用类型的默认输出格式的标识符。这些标识符所对应的值从格式化字符串后的第一个逗号开始按照相同顺序添加,如果参数超过 1 个则同样需要使用逗号分隔。使用这些占位符可以很好地控制格式化输出的文本
 
-函数 **fmt.Sprintf** 与 **Printf** 的行为完全相同。但是只简单的返回格式化后的字符串:所以可以在你的程序中使用字符串包含变量值使用(例子,见 Listing 15.4-simple_tcp_server.go)
+函数 `fmt.Sprintf` 与 `Printf` 的作用是完全相同的,不过前者将格式化后的字符串以返回值的形式返回给调用者,因此你可以在程序中使用包含变量的字符串,具体例子可以参见 Example 15.4 [simple_tcp_server.go](examples/chapter_15/simple_tcp_server.go)
 
-函数 **fmt.Print** 和 **fmt.Println** 表现完全自动化使用格式化标识符 **%v** 进行格式化,在每个参数后添加空格,后者在最后添加一个换行符。所以 **fmt.Print("Hello:", 23)** 将输出:**Hello: 23**
+函数 `fmt.Print` 和 `fmt.Println` 会自动使用格式化标识符 `%v` 对字符串进行格式化,两者都会在每个参数之间自动增加空格,而后者还会在字符串的最后加上一个换行符。例如:`fmt.Print("Hello:", 23)` 将输出:`Hello: 23`。
 
-## 4.4.4 简短形式,使用 := 赋值操作符
+##4.4.4 简短形式,使用 := 赋值操作符
+我们知道可以在变量的初始化时省略变量的类型而由系统自动推断,而这个时候再在 Example 4.4.1 的最后一个声明语句写上 `var` 关键字就显得有些多余了,因此我们可以将它们简写为 `a := 50` 或 `b := false`。
 
-忽略类型,关键字 var 在 § 4.4.1 最后一段中是非常多于的,我们可以简写为:**a := 50** 或 **b := false**
+a 和 b 的类型(int 和 bool)将由编译器自动推断
 
-a 和 b 的类型(int 和 bool)将被编译器推断出
+这是使用变量的首选形式,但是它只能被用在函数体内,而不可以用于全局变量的声明与赋值。使用操作符 `:=` 可以高效地创建一个新的变量,称之为初始化声明
 
-这是首选形式,但它只能在*函数内部使用,而不是在包的范围*。 操作符 := 将有效地创建一个新的变量,它也被称为初始化声明。
+**注意事项**
 
-*提醒*:如果在这行之后写相同的代码块,如我们声明 a := 20,这是不允许的:编译器会给出错误提示 “**no new variables on left side of :=**”;但 a = 20 是可以的,因为这是给相同的变量一个新的值。
+如果在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明,例如:`a := 20` 就是不被允许的,编译器会提示错误 `no new variables on left side of :=`,但是 `a = 20` 是可以的,因为这是给相同的变量赋予一个新的值。
 
-一个变量 a 被使用,但是没有定义,会得到一个编译错误:**undefined: a**
+如果你在定义变量 a 之前使用它,则会得到编译错误 `undefined: a`。
 
-声明一个*本地*变量,但是不使用它,也会得到编译错误;如变量 a 在如下的 main 函数中
+如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误,例如下面这个例子当中的变量 a
 
     func main() {
         var a string = "abc"
         fmt.Println(“hello, world”)
     }
 
-将得到错误:**a declared and not used**
+尝试编译这段代码将得到错误 `a declared and not used`。
 
-当然,设置 a 的值也不是足够的,这个值必须被使用,所以 **fmt.Println("hello, world", a)** 会移除错误。
+此外,单纯地给 a 赋值也是不够的,这个值必须被使用,所以使用 `fmt.Println("hello, world", a)` 会移除错误。
 
-但是全局变量是允许这样的
+但是全局变量是允许声明但不使用
 
 其他的简短形式为:
 
-同一类型的多个变量可以声明在一行,如:**var a, b, c int**
+同一类型的多个变量可以声明在同一行,如:`var a, b, c int`。
 
-(这是类型写在标识符后面的重要原因)
+(这是类型写在标识符后面的一个重要原因)
 
-多变量可以在同一行进行赋值,如:**a, b, c = 5, 7, "abc"**
+多变量可以在同一行进行赋值,如:`a, b, c = 5, 7, "abc"`。
 
-这假设了变量 a,b 和 c 都被声明了,否则应这样:**a, b, c := 5, 7, "abc"**
+上面这行假设了变量 a,b 和 c 都已经被声明,否则的话应该这样使用:`a, b, c := 5, 7, "abc"`。
 
-右边的这些值以相同的顺序赋值给左边的变量,所以 a 的值是 5, b 的值是 7,c 的值是  "abc"
+右边的这些值以相同的顺序赋值给左边的变量,所以 a 的值是 `5`, b 的值是 `7`,c 的值是 `"abc"`
 
-这被称为*并行或同时赋值*
+这被称为 **并行**  **同时** 赋值。
 
-使用两个变量,它可以被用来执行交换的值:**a, b = b, a**
+如果你想要交换两个变量的值,则可以简单地使用 `a, b = b, a`。
 
-(在 Go 语言,这样省去了使用交换函数的必要)
+(在 Go 语言,这样省去了使用交换函数的必要)
 
-空标识符 _ 也被用于扔掉值,如值 5 在:**_, b = 5, 7** 被扔掉
+空白标识符 `_` 也被用于抛弃值,如值 `5` 在:`_, b = 5, 7` 中被抛弃
 
-_ 实际上是一个只写变量,你不能得到它的值。这样做是因为一个声明的变量必须在 Go 语言中必须使用,但有时你并不需要使用从一个函数得到的所有返回值。
+`_` 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。
 
-多赋值也被用于当一个函数返回多于一个值,如这里 val 和一个错误 err 被 Func1 这个函数返回:**val, err = Func1(var1)**
+并行赋值也被用于当一个函数返回多个返回值时,比如这里的 `val` 和错误 `err` 是通过调用 `Func1` 函数同时得到:`val, err = Func1(var1)`。
 
-## 4.4.5 初始函数
+##4.4.5 init 函数
+变量除了可以在全局声明中初始化,也可以在 init() 函数中初始化。这是一类非常特殊的函数,它不能够被人为调用,而是在每个包完成初始化后自动执行,并且执行优先级在 main() 函数高。
 
-除了在全局声明中初始化,变量也可以在一个 init() 函数中初始化。这是一个特殊的函数名称 init(),它不能被调用,但在 package main 中自动在 main() 函数之前,或者自动在导入含有该函数的包之前执行。
+每一个源文件都可以包含且只包含一个 init() 函数。初始化总是以单线程执行,并且按照包的依赖关系顺序执行。
 
-每一个源文件都可以包含且只包含一个 init() 方法。初始化总是单线程的,并且包依赖关系保证其正确的执行顺序
+一个可能的用途是在开始执行程序之前对数据进行检验或修复,以保证程序状态的正确性
 
-一个可能的用途是在真正执行之前,检验或修复程序状态的正确性。
-
-例子: Listing 4.6—[init.go](examples/chapter_4/init.go):
+Example 4.6 [init.go](examples/chapter_4/init.go):
 
     package trans
     import "math"
@@ -234,9 +219,9 @@ _ 实际上是一个只写变量,你不能得到它的值。这样做是因为
         Pi = 4 * math.Atan(1) // init() function computes Pi
     }
 
-在它的 init() 函数中,变量 Pi 被计算初始值。
+在它的 init() 函数中计算变量 Pi 的初始值。
 
-程序在 Listing 4.7 [user_init.go](examples/chapter_4/user_init.go) 中导入了包 trans (在相同的路径中) 并且使用 Pi:
+Example 4.7 [user_init.go](examples/chapter_4/user_init.go) 中导入了包 trans(在相同的路径中)并且使用到了变量 Pi:
 
     package main
     import (
@@ -248,16 +233,16 @@ _ 实际上是一个只写变量,你不能得到它的值。这样做是因为
         fmt.Printf("2*Pi = %g\n", twoPi) // 2*Pi = 6.283185307179586
     }
 
-init() 函数也经常被用在当一个程序开始之前,一个 backend() goroutine 需要被执行,如
+init() 函数也经常被用在当一个程序开始之前调用后台执行的 goroutine,如下面这个例子当中的 `backend()`
 
     func init() {
         // setup preparations
         go backend()
     }
 
-*练习*:推断以下程序的输出,并解释你的答案,然后编译并执行它们。
+**练习** 推断以下程序的输出,并解释你的答案,然后编译并执行它们。
 
-练习 4.1: [local_scope.go](examples/chapter_4/local_scope.go):
+练习 4.1 [local_scope.go](examples/chapter_4/local_scope.go):
 
     package main
     var a = "G"
@@ -272,7 +257,7 @@ init() 函数也经常被用在当一个程序开始之前,一个 backend() go
         print(a)
     }
 
-练习 4.2: [global_scope.go](examples/chapter_4/global_scope.go):
+练习 4.2 [global_scope.go](examples/chapter_4/global_scope.go):
 
     package main
     var a = "G"
@@ -289,7 +274,7 @@ init() 函数也经常被用在当一个程序开始之前,一个 backend() go
         print(a)
     }
 
-练习 4.3: [function_calls_function.go](examples/chapter_4/function_calls_function.go)
+练习 4.3 [function_calls_function.go](examples/chapter_4/function_calls_function.go)
 
     package main
     var a string
@@ -307,7 +292,6 @@ init() 函数也经常被用在当一个程序开始之前,一个 backend() go
         print(a)
     }
 
-
 ##链接
 - [目录](directory.md)
 - 上一节:[常量](04.3.md)

+ 22 - 0
eBook/04.5.md

@@ -0,0 +1,22 @@
+##啊哦,亲,你看得也太快了。。。还没翻译完呢 0 0
+要不等到 ***2013 年 5 月 9 日*** 再来看看吧~~
+
+这里还有一些其它的学习资源噢~
+
+ - [《Go编程基础》](https://github.com/Unknwon/go-fundamental-programming):已更新至 [第9课](https://github.com/Unknwon/go-fundamental-programming/blob/master/lecture9/lecture9.md) 
+ - [《Go Web编程》](https://github.com/astaxie/build-web-application-with-golang)
+
+神马?你说你不想学习?那好吧,去逛逛看看行情也行~
+
+- [Go Walker](http://gowalker.org) **Go 项目文档在线浏览工具**
+- [Golang中文社区](http://bbs.mygolang.com/forum.php)
+- [Go语言学习园地](http://studygolang.com/)
+- [Golang中国](http://golang.tc)
+
+#4.5 基本类型和运算符
+
+
+##链接
+- [目录](directory.md)
+- 上一节:[变量](04.4.md)
+- 下一节:[字符串](04.6.md)

+ 1 - 0
eBook/directory.md

@@ -32,6 +32,7 @@
 	- 4.3 [常量](04.3.md)
 	- 4.4 [变量](04.4.md)
 	- 4.5 [基本类型和运算符](04.5.md)
+	- 4.6 [字符串](04.6.md)
 - 第5章:[控制结构](05.0.md)
 	- 5.1 [if-else 结构](05.1.md)
 - 第6章:函数(function)

+ 1027 - 0
eBook/examples/chapter_15/Codelab Writing Web Applications - The Go Programming Language.htm

@@ -0,0 +1,1027 @@
+<!DOCTYPE html>
+<!-- saved from url=(0035)http://golang.org/doc/codelab/wiki/ -->
+<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+
+  <title>Codelab: Writing Web Applications - The Go Programming Language</title>
+<link rel="stylesheet" href="./Codelab  Writing Web Applications - The Go Programming Language_files/all.css" type="text/css" media="all" charset="utf-8">
+<!--[if lt IE 8]>
+<link rel="stylesheet" href="/doc/ie.css" type="text/css">
+<![endif]-->
+<script type="text/javascript" async="" src="./Codelab  Writing Web Applications - The Go Programming Language_files/ga.js"></script><script type="text/javascript" src="./Codelab  Writing Web Applications - The Go Programming Language_files/godocs.js"></script>
+<script type="text/javascript">
+var _gaq = _gaq || [];
+_gaq.push(["_setAccount", "UA-11222381-2"]);
+_gaq.push(["_trackPageview"]);
+</script>
+</head>
+<body id="top">
+<div id="container">
+  <div id="topnav">
+    <h1 id="title">The Go Programming Language</h1>
+    <div id="nav-main">
+      <ul>
+      <li><a href="http://golang.org/">Home</a></li><li><a href="http://golang.org/doc/install.html">Getting Started</a></li><li><a href="http://golang.org/doc/docs.html">Documentation</a></li><li><a href="http://golang.org/doc/contrib.html">Contributing</a></li><li><a href="http://golang.org/doc/community.html">Community</a></li>
+      </ul>
+      <div class="quickref">
+        <form method="GET" action="http://golang.org/search">
+        References:
+        <a href="http://golang.org/pkg/">Packages</a> <span class="sep">|</span>
+        <a href="http://golang.org/cmd/">Commands</a> <span class="sep">|</span>
+        <a href="http://golang.org/doc/go_spec.html">Specification</a>
+        <input id="search" type="search" name="q" value="" class="inactive" placeholder="code search" results="0">
+        </form>
+      </div>
+    </div>
+    <a id="logo-box" href="http://golang.org/"></a>
+  </div>
+  <div id="content">
+  <!-- Menu is HTML-escaped elsewhere -->
+
+    <h1 id="generatedHeader">Codelab: Writing Web Applications</h1>
+
+  <!-- The Table of Contents is automatically inserted in this <div>.
+       Do not delete this <div>. -->
+  <div id="nav"><table class="unruled"><tbody><tr><td class="first"><dl><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_13">Introduction</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_23">Getting Started</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_39">Data Structures</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_87">Introducing the http package (an interlude)</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_111">Using http to serve wiki pages</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_139">Editing Pages</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_153">The template package</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_191">Handling non-existent pages</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_199">Saving Pages</a></dt></dl></td><td><dl><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_209">Error handling</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_225">Template caching</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_242">Validation</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_262">Introducing Function Literals and Closures</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_290">Try it out!</a></dt><dt><a href="http://golang.org/doc/codelab/wiki/#tmp_300">Other tasks</a></dt></dl></td></tr></tbody></table></div>
+
+  <!-- Content is HTML-escaped elsewhere -->
+  <!-- Codelab: Writing Web Applications -->
+<h2 id="tmp_13">Introduction<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></h2>
+
+<p>
+Covered in this codelab:
+</p>
+<ul>
+<li>Creating a data structure with load and save methods</li>
+<li>Using the <code>http</code> package to build web applications
+</li><li>Using the <code>template</code> package to process HTML templates</li>
+<li>Using the <code>regexp</code> package to validate user input</li>
+<li>Using closures</li>
+</ul>
+
+<p>
+Assumed knowledge:
+</p>
+<ul>
+<li>Programming experience</li>
+<li>Understanding of basic web technologies (HTTP, HTML)</li>
+<li>Some UNIX command-line knowledge</li>
+</ul>
+
+<h2 id="tmp_23">Getting Started<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></h2>
+
+<p>
+At present, you need to have a Linux, OS X, or FreeBSD machine to run Go. If
+you don't have access to one, you could set up a Linux Virtual Machine (using 
+<a href="http://www.virtualbox.org/">VirtualBox</a> or similar) or a
+<a href="http://www.google.com/search?q=virtual+private+server">Virtual 
+Private Server</a>.
+</p>
+
+<p>
+Install Go (see the <a href="http://golang.org/doc/install.html">Installation Instructions</a>).
+</p>
+
+<p>
+Make a new directory for this codelab and cd to it:
+</p>
+
+<pre>$ mkdir ~/gowiki
+$ cd ~/gowiki
+</pre>
+
+<p>
+Create a file named <code>wiki.go</code>, open it in your favorite editor, and 
+add the following lines:
+</p>
+
+<pre>package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+)
+</pre>
+
+<p>
+We import the <code>fmt</code>, <code>ioutil</code> and <code>os</code>
+packages from the Go standard library. Later, as we implement additional
+functionality, we will add more packages to this <code>import</code>
+declaration.
+</p>
+
+<h2 id="tmp_39">Data Structures<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></h2>
+
+<p>
+Let's start by defining the data structures. A wiki consists of a series of
+interconnected pages, each of which has a title and a body (the page content).
+Here, we define <code>Page</code> as a struct with two fields representing
+the title and body.
+</p>
+
+<pre>type Page struct {
+	Title	string
+	Body	[]byte
+}
+</pre>
+
+<p>
+The type <code>[]byte</code> means "a <code>byte</code> slice". 
+(See <a href="http://golang.org/doc/effective_go.html#slices">Effective Go</a> 
+for more on slices.)  
+The <code>Body</code> element is a <code>[]byte</code> rather than
+<code>string</code> because that is the type expected by the <code>io</code>
+libraries we will use, as you'll see below.
+</p>
+
+<p>
+The <code>Page</code> struct describes how page data will be stored in memory. 
+But what about persistent storage? We can address that by creating a 
+<code>save</code> method on <code>Page</code>:
+</p>
+
+<pre>func (p *Page) save() os.Error {
+	filename := p.Title + ".txt"
+	return ioutil.WriteFile(filename, p.Body, 0600)
+}
+</pre>
+
+<p>
+This method's signature reads: "This is a method named <code>save</code> that
+takes as its receiver <code>p</code>, a pointer to <code>Page</code> . It takes
+no parameters, and returns a value of type <code>os.Error</code>." 
+</p>
+
+<p>
+This method will save the <code>Page</code>'s <code>Body</code> to a text 
+file. For simplicity, we will use the <code>Title</code> as the file name.
+</p>
+
+<p>
+The <code>save</code> method returns an <code>os.Error</code> value because
+that is the return type of <code>WriteFile</code> (a standard library function
+that writes a byte slice to a file).  The <code>save</code> method returns the
+error value, to let the application handle it should anything go wrong while
+writing the file.  If all goes well, <code>Page.save()</code> will return
+<code>nil</code> (the zero-value for pointers, interfaces, and some other 
+types).
+</p>
+
+<p>
+The octal integer constant <code>0600</code>, passed as the third parameter to
+<code>WriteFile</code>, indicates that the file should be created with
+read-write permissions for the current user only. (See the Unix man page
+<code>open(2)</code> for details.)
+</p>
+
+<p>
+We will want to load pages, too:
+</p>
+
+<pre>func loadPage(title string) *Page {
+	filename := title + ".txt"
+	body, _ := ioutil.ReadFile(filename)
+	return &amp;Page{Title: title, Body: body}
+}
+</pre>
+
+<p>
+The function <code>loadPage</code> constructs the file name from
+<code>Title</code>, reads the file's contents into a new
+<code>Page</code>, and returns a pointer to that new <code>page</code>.
+</p>
+
+<p>
+Functions can return multiple values. The standard library function 
+<code>io.ReadFile</code> returns <code>[]byte</code> and <code>os.Error</code>. 
+In <code>loadPage</code>, error isn't being handled yet; the "blank identifier"
+represented by the underscore (<code>_</code>) symbol is used to throw away the
+error return value (in essence, assigning the value to nothing). 
+</p>
+
+<p>
+But what happens if <code>ReadFile</code> encounters an error?  For example,
+the file might not exist. We should not ignore such errors.  Let's modify the
+function to return <code>*Page</code> and <code>os.Error</code>.
+</p>
+
+<pre>func loadPage(title string) (*Page, os.Error) {
+	filename := title + ".txt"
+	body, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return nil, err
+	}
+	return &amp;Page{Title: title, Body: body}, nil
+}
+</pre>
+
+<p>
+Callers of this function can now check the second parameter; if it is
+<code>nil</code> then it has successfully loaded a Page. If not, it will be an
+<code>os.Error</code> that can be handled by the caller (see the <a href="http://golang.org/pkg/os/#Error">os package documentation</a> for 
+details).
+</p>
+
+<p>
+At this point we have a simple data structure and the ability to save to and
+load from a file. Let's write a <code>main</code> function to test what we've
+written:
+</p>
+
+<pre>func main() {
+	p1 := &amp;Page{Title: "TestPage", Body: []byte("This is a sample Page.")}
+	p1.save()
+	p2, _ := loadPage("TestPage")
+	fmt.Println(string(p2.Body))
+}
+</pre>
+
+<p>
+After compiling and executing this code, a file named <code>TestPage.txt</code>
+would be created, containing the contents of <code>p1</code>. The file would
+then be read into the struct <code>p2</code>, and its <code>Body</code> element
+printed to the screen.
+</p>
+
+<p>
+You can compile and run the program like this: 
+</p>
+
+<pre>$ 8g wiki.go
+$ 8l wiki.8
+$ ./8.out
+This is a sample page.
+</pre>
+
+<p>
+(The <code>8g</code> and <code>8l</code> commands are applicable to
+<code>GOARCH=386</code>. If you're on an <code>amd64</code> system,
+substitute 6's for the 8's.)
+</p>
+
+<p>
+<a href="http://golang.org/doc/codelab/wiki/part1.go">Click here to view the code we've written so far.</a>
+</p>
+
+<h2 id="tmp_87">Introducing the <code>http</code> package (an interlude)<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></h2>
+
+<p>
+Here's a full working example of a simple web server:
+</p>
+
+<pre>package main
+
+import (
+	"fmt"
+	"http"
+)
+
+func handler(w http.ResponseWriter, r *http.Request) {
+	fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
+}
+
+func main() {
+	http.HandleFunc("/", handler)
+	http.ListenAndServe(":8080", nil)
+}
+</pre>
+
+<p>
+The <code>main</code> function begins with a call to 
+<code>http.HandleFunc</code>, which tells the <code>http</code> package to 
+handle all requests to the web root (<code>"/"</code>) with 
+<code>handler</code>. 
+</p>
+
+<p>
+It then calls <code>http.ListenAndServe</code>, specifying that it should
+listen on port 8080 on any interface (<code>":8080"</code>). (Don't
+worry about its second parameter, <code>nil</code>, for now.)
+This function will block until the program is terminated.
+</p>
+
+<p>
+The function <code>handler</code> is of the type <code>http.HandlerFunc</code>.
+It takes an <code>http.ResponseWriter</code> and an <code>http.Request</code> as
+its arguments.
+</p>
+
+<p>
+An <code>http.ResponseWriter</code> value assembles the HTTP server's response; by writing 
+to it, we send data to the HTTP client.
+</p>
+
+<p>
+An <code>http.Request</code> is a data structure that represents the client
+HTTP request.  The string <code>r.URL.Path</code> is the path component
+of the request URL.  The trailing <code>[1:]</code> means
+"create a sub-slice of <code>Path</code> from the 1st character to the end." 
+This drops the leading "/" from the path name.
+</p>
+
+<p>
+If you run this program and access the URL: 
+</p>
+<pre>http://localhost:8080/monkeys</pre>
+<p>
+the program would present a page containing:
+</p>
+<pre>Hi there, I love monkeys!</pre>
+
+<h2 id="tmp_111">Using <code>http</code> to serve wiki pages<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></h2>
+
+<p>
+To use the <code>http</code> package, it must be imported:
+</p>
+
+<pre>import (
+	"fmt"
+	<b>"http"</b>
+	"io/ioutil"
+	"os"
+)
+</pre>
+
+<p>
+Let's create a handler to view a wiki page: 
+</p>
+
+<pre>const lenPath = len("/view/")
+
+func viewHandler(w http.ResponseWriter, r *http.Request) {
+	title := r.URL.Path[lenPath:]
+	p, _ := loadPage(title)
+	fmt.Fprintf(w, "&lt;h1&gt;%s&lt;/h1&gt;&lt;div&gt;%s&lt;/div&gt;", p.Title, p.Body)
+}
+</pre>
+
+<p>
+First, this function extracts the page title from <code>r.URL.Path</code>,
+the path component of the request URL. The global constant 
+<code>lenPath</code> is the length of the leading <code>"/view/"</code>
+component of the request path.
+The <code>Path</code> is re-sliced with <code>[lenPath:]</code> to drop the 
+first 6 characters of the string. This is because the path will invariably 
+begin with <code>"/view/"</code>, which is not part of the page title.
+</p>
+
+<p>
+The function then loads the page data, formats the page with a string of simple 
+HTML, and writes it to <code>w</code>, the <code>http.ResponseWriter</code>. 
+</p>
+
+<p>
+Again, note the use of <code>_</code> to ignore the <code>os.Error</code> 
+return value from <code>loadPage</code>. This is done here for simplicity
+and generally considered bad practice. We will attend to this later.
+</p>
+
+<p>
+To use this handler, we create a <code>main</code> function that
+initializes <code>http</code> using the <code>viewHandler</code> to handle
+any requests under the path <code>/view/</code>.
+</p>
+
+<pre>func main() {
+	http.HandleFunc("/view/", viewHandler)
+	http.ListenAndServe(":8080", nil)
+}
+</pre>
+
+<p>
+<a href="http://golang.org/doc/codelab/wiki/part2.go">Click here to view the code we've written so far.</a>
+</p>
+
+<p>
+Let's create some page data (as <code>test.txt</code>), compile our code, and
+try serving a wiki page:
+</p>
+
+<pre>$ echo "Hello world" &gt; test.txt
+$ 8g wiki.go
+$ 8l wiki.8
+$ ./8.out
+</pre>
+
+<p>
+With this web server running, a visit to <code><a href="http://localhost:8080/view/test">http://localhost:8080/view/test</a></code>
+should show a page titled "test" containing the words "Hello world".
+</p>
+
+<h2 id="tmp_139">Editing Pages<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></h2>
+
+<p>
+A wiki is not a wiki without the ability to edit pages. Let's create two new
+handlers: one named <code>editHandler</code> to display an 'edit page' form,
+and the other named <code>saveHandler</code> to save the data entered via the
+form.
+</p>
+
+<p>
+First, we add them to <code>main()</code>: 
+</p>
+
+<pre>func main() {
+	http.HandleFunc("/view/", viewHandler)
+	http.HandleFunc("/edit/", editHandler)
+	http.HandleFunc("/save/", saveHandler)
+	http.ListenAndServe(":8080", nil)
+}
+</pre>
+
+<p>
+The function <code>editHandler</code> loads the page 
+(or, if it doesn't exist, create an empty <code>Page</code> struct), 
+and displays an HTML form.
+</p>
+
+<pre>func editHandler(w http.ResponseWriter, r *http.Request) {
+	title := r.URL.Path[lenPath:]
+	p, err := loadPage(title)
+	if err != nil {
+		p = &amp;Page{Title: title}
+	}
+	fmt.Fprintf(w, "&lt;h1&gt;Editing %s&lt;/h1&gt;"+
+		"&lt;form action=\"/save/%s\" method=\"POST\"&gt;"+
+		"&lt;textarea name=\"body\"&gt;%s&lt;/textarea&gt;&lt;br&gt;"+
+		"&lt;input type=\"submit\" value=\"Save\"&gt;"+
+		"&lt;/form&gt;",
+		p.Title, p.Title, p.Body)
+}
+</pre>
+
+<p>
+This function will work fine, but all that hard-coded HTML is ugly.
+Of course, there is a better way.
+</p>
+ 
+<h2 id="tmp_153">The <code>template</code> package<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></h2>
+
+<p>
+The <code>template</code> package is part of the Go standard library.  We can
+use <code>template</code> to keep the HTML in a separate file, allowing
+us to change the layout of our edit page without modifying the underlying Go
+code.
+</p>
+
+<p>
+First, we must add <code>template</code> to the list of imports:
+</p>
+
+<pre>import (
+	"http"
+	"io/ioutil"
+	"os"
+	<b>"template"</b>
+)
+</pre>
+
+<p>
+Let's create a template file containing the HTML form. 
+Open a new file named <code>edit.html</code>, and add the following lines:
+</p>
+
+<pre>&lt;h1&gt;Editing {Title}&lt;/h1&gt;
+
+&lt;form action="/save/{Title}" method="POST"&gt;
+&lt;div&gt;&lt;textarea name="body" rows="20" cols="80"&gt;{Body|html}&lt;/textarea&gt;&lt;/div&gt;
+&lt;div&gt;&lt;input type="submit" value="Save"&gt;&lt;/div&gt;
+&lt;/form&gt;
+</pre>
+
+<p>
+Modify <code>editHandler</code> to use the template, instead of the hard-coded
+HTML:
+</p>
+
+<pre>func editHandler(w http.ResponseWriter, r *http.Request) {
+	title := r.URL.Path[lenPath:]
+	p, err := loadPage(title)
+	if err != nil {
+		p = &amp;Page{Title: title}
+	}
+	t, _ := template.ParseFile("edit.html", nil)
+	t.Execute(w, p)
+}
+</pre>
+
+<p>
+The function <code>template.ParseFile</code> will read the contents of 
+<code>edit.html</code> and return a <code>*template.Template</code>. 
+</p>
+
+<p>
+The method <code>t.Execute</code> replaces all occurrences of 
+<code>{Title}</code> and <code>{Body}</code> with the values of 
+<code>p.Title</code> and <code>p.Body</code>, and writes the resultant
+HTML to the <code>http.ResponseWriter</code>.
+</p>
+
+<p>
+Note that we've used <code>{Body|html}</code> in the above template.  
+The <code>|html</code> part asks the template engine to pass the value
+<code>Body</code> through the <code>html</code> formatter before outputting it,
+which escapes HTML characters (such as replacing <code>&gt;</code> with 
+<code>&amp;gt;</code>). 
+This will prevent user data from corrupting the form HTML. 
+</p>
+
+<p>
+Now that we've removed the <code>fmt.Fprintf</code> statement, we can remove
+<code>"fmt"</code> from the <code>import</code> list.
+</p>
+
+<p>
+While we're working with templates, let's create a template for our
+<code>viewHandler</code> called <code>view.html</code>:
+</p>
+
+<pre>&lt;h1&gt;{Title}&lt;/h1&gt;
+
+&lt;p&gt;[&lt;a href="/edit/{Title}"&gt;edit&lt;/a&gt;]&lt;/p&gt;
+
+&lt;div&gt;{Body}&lt;/div&gt;
+</pre>
+
+<p>
+Modify <code>viewHandler</code> accordingly:
+</p>
+
+<pre>func viewHandler(w http.ResponseWriter, r *http.Request) {
+	title := r.URL.Path[lenPath:]
+	p, _ := loadPage(title)
+	t, _ := template.ParseFile("view.html", nil)
+	t.Execute(w, p)
+}
+</pre>
+
+<p>
+Notice that we've used almost exactly the same templating code in both
+handlers. Let's remove this duplication by moving the templating code
+to its own function:
+</p>
+
+<pre>func viewHandler(w http.ResponseWriter, r *http.Request) {
+	title := r.URL.Path[lenPath:]
+	p, _ := loadPage(title)
+	renderTemplate(w, "view", p)
+}
+
+func editHandler(w http.ResponseWriter, r *http.Request) {
+	title := r.URL.Path[lenPath:]
+	p, err := loadPage(title)
+	if err != nil {
+		p = &amp;Page{Title: title}
+	}
+	renderTemplate(w, "edit", p)
+}
+
+func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
+	t, _ := template.ParseFile(tmpl+".html", nil)
+	t.Execute(w, p)
+}
+</pre>
+
+<p>
+The handlers are now shorter and simpler. 
+</p>
+
+<h2 id="tmp_191">Handling non-existent pages<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></h2>
+
+<p>
+What if you visit <code>/view/APageThatDoesntExist</code>? The program will 
+crash. This is because it ignores the error return value from
+<code>loadPage</code>. Instead, if the requested Page doesn't exist, it should 
+redirect the client to the edit Page so the content may be created:
+</p>
+
+<pre>func viewHandler(w http.ResponseWriter, r *http.Request) {
+	title, err := getTitle(w, r)
+	if err != nil {
+		return
+	}
+	p, err := loadPage(title)
+	if err != nil {
+		http.Redirect(w, r, "/edit/"+title, http.StatusFound)
+		return
+	}
+	renderTemplate(w, "view", p)
+}
+</pre>
+
+<p>
+The <code>http.Redirect</code> function adds an HTTP status code of 
+<code>http.StatusFound</code> (302) and a <code>Location</code>
+header to the HTTP response.
+</p>
+
+<h2 id="tmp_199">Saving Pages<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></h2>
+
+<p>
+The function <code>saveHandler</code> will handle the form submission. 
+</p>
+
+<pre>func saveHandler(w http.ResponseWriter, r *http.Request) {
+	title := r.URL.Path[lenPath:]
+	body := r.FormValue("body")
+	p := &amp;Page{Title: title, Body: []byte(body)}
+	p.save()
+	http.Redirect(w, r, "/view/"+title, http.StatusFound)
+}
+</pre>
+
+<p>
+The page title (provided in the URL) and the form's only field, 
+<code>Body</code>, are stored in a new <code>Page</code>. 
+The <code>save()</code> method is then called to write the data to a file,
+and the client is redirected to the <code>/view/</code> page.
+</p>
+
+<p>
+The value returned by <code>FormValue</code> is of type <code>string</code>.
+We must convert that value to <code>[]byte</code> before it will fit into 
+the <code>Page</code> struct.  We use <code>[]byte(body)</code> to perform
+the conversion.
+</p>
+
+<h2 id="tmp_209">Error handling<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></h2>
+
+<p>
+There are several places in our program where errors are being ignored.  This
+is bad practice, not least because when an error does occur the program will
+crash.  A better solution is to handle the errors and return an error message
+to the user. That way if something does go wrong, the server will continue to
+function and the user will be notified.
+</p>
+
+<p>
+First, let's handle the errors in <code>renderTemplate</code>:
+</p>
+
+<pre>func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
+	t, err := template.ParseFile(tmpl+".html", nil)
+	if err != nil {
+		http.Error(w, err.String(), http.StatusInternalServerError)
+		return
+	}
+	err = t.Execute(w, p)
+	if err != nil {
+		http.Error(w, err.String(), http.StatusInternalServerError)
+	}
+}
+</pre>
+
+<p>
+The <code>http.Error</code> function sends a specified HTTP response code 
+(in this case "Internal Server Error") and error message.
+Already the decision to put this in a separate function is paying off.
+</p>
+
+<p>
+Now let's fix up <code>saveHandler</code>:
+</p>
+
+<pre>func saveHandler(w http.ResponseWriter, r *http.Request) {
+	title, err := getTitle(w, r)
+	if err != nil {
+		return
+	}
+	body := r.FormValue("body")
+	p := &amp;Page{Title: title, Body: []byte(body)}
+	err = p.save()
+	if err != nil {
+		http.Error(w, err.String(), http.StatusInternalServerError)
+		return
+	}
+	http.Redirect(w, r, "/view/"+title, http.StatusFound)
+}
+</pre>
+
+<p>
+Any errors that occur during <code>p.save()</code> will be reported 
+to the user.
+</p>
+
+<h2 id="tmp_225">Template caching<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></h2>
+
+<p>
+There is an inefficiency in this code: <code>renderTemplate</code> calls 
+<code>ParseFile</code> every time a page is rendered. 
+A better approach would be to call <code>ParseFile</code> once for each 
+template at program initialization, and store the resultant 
+<code>*Template</code> values in a data structure for later use.
+</p>
+
+<p>
+First we create a global map named <code>templates</code> in which to store 
+our <code>*Template</code> values, keyed by <code>string</code> 
+(the template name):
+</p>
+
+<pre>var templates = make(map[string]*template.Template)
+</pre>
+
+<p>
+Then we create an <code>init</code> function, which will be called before
+<code>main</code> at program initialization. The function
+<code>template.MustParseFile</code> is a convenience wrapper around
+<code>ParseFile</code> that does not return an error code; instead, it panics
+if an error is encountered. A panic is appropriate here; if the templates can't
+be loaded the only sensible thing to do is exit the program.
+</p>
+
+<pre>func init() {
+	for _, tmpl := range []string{"edit", "view"} {
+		templates[tmpl] = template.MustParseFile(tmpl+".html", nil)
+	}
+}
+</pre>
+
+<p>
+A <code>for</code> loop is used with a <code>range</code> statement to iterate 
+over an array constant containing the names of the templates we want parsed.
+If we were to add more templates to our program, we would add their names to 
+that array.
+</p>
+
+<p>
+We then modify our <code>renderTemplate</code> function to call 
+the <code>Execute</code> method on the appropriate <code>Template</code> from 
+<code>templates</code>:
+
+</p><pre>func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
+	err := templates[tmpl].Execute(w, p)
+	if err != nil {
+		http.Error(w, err.String(), http.StatusInternalServerError)
+	}
+}
+</pre>
+
+<h2 id="tmp_242">Validation<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></h2>
+
+<p>
+As you may have observed, this program has a serious security flaw: a user
+can supply an arbitrary path to be read/written on the server. To mitigate
+this, we can write a function to validate the title with a regular expression.
+</p>
+
+<p>
+First, add <code>"regexp"</code> to the <code>import</code> list.
+Then we can create a global variable to store our validation regexp:
+</p>
+
+<pre>var titleValidator = regexp.MustCompile("^[a-zA-Z0-9]+$")
+</pre>
+
+<p>
+The function <code>regexp.MustCompile</code> will parse and compile the 
+regular expression, and return a <code>regexp.Regexp</code>. 
+<code>MustCompile</code>, like <code>template.MustParseFile</code>,
+is distinct from <code>Compile</code> in that it will panic if 
+the expression compilation fails, while <code>Compile</code> returns an 
+<code>os.Error</code> as a second parameter. 
+</p>
+
+<p>
+Now, let's write a function that extracts the title string from the request 
+URL, and tests it against our <code>TitleValidator</code> expression:
+</p>
+
+<pre>func getTitle(w http.ResponseWriter, r *http.Request) (title string, err os.Error) {
+	title = r.URL.Path[lenPath:]
+	if !titleValidator.MatchString(title) {
+		http.NotFound(w, r)
+		err = os.NewError("Invalid Page Title")
+	}
+	return
+}
+</pre>
+
+<p>
+If the title is valid, it will be returned along with a <code>nil</code>
+error value.  If the title is invalid, the function will write a 
+"404 Not Found" error to the HTTP connection, and return an error to the 
+handler. 
+</p>
+
+<p>
+Let's put a call to <code>getTitle</code> in each of the handlers:
+</p>
+
+<pre>func viewHandler(w http.ResponseWriter, r *http.Request) {
+	title, err := getTitle(w, r)
+	if err != nil {
+		return
+	}
+	p, err := loadPage(title)
+	if err != nil {
+		http.Redirect(w, r, "/edit/"+title, http.StatusFound)
+		return
+	}
+	renderTemplate(w, "view", p)
+}
+
+func editHandler(w http.ResponseWriter, r *http.Request) {
+	title, err := getTitle(w, r)
+	if err != nil {
+		return
+	}
+	p, err := loadPage(title)
+	if err != nil {
+		p = &amp;Page{Title: title}
+	}
+	renderTemplate(w, "edit", p)
+}
+
+func saveHandler(w http.ResponseWriter, r *http.Request) {
+	title, err := getTitle(w, r)
+	if err != nil {
+		return
+	}
+	body := r.FormValue("body")
+	p := &amp;Page{Title: title, Body: []byte(body)}
+	err = p.save()
+	if err != nil {
+		http.Error(w, err.String(), http.StatusInternalServerError)
+		return
+	}
+	http.Redirect(w, r, "/view/"+title, http.StatusFound)
+}
+</pre>
+
+<h2 id="tmp_262">Introducing Function Literals and Closures<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></h2>
+
+<p>
+Catching the error condition in each handler introduces a lot of repeated code.
+What if we could wrap each of the handlers in a function that does this 
+validation and error checking? Go's 
+<a href="http://golang.org/doc/go_spec.html#Function_declarations">function 
+literals</a> provide a powerful means of abstracting functionality 
+that can help us here.
+</p>
+
+<p>
+First, we re-write the function definition of each of the handlers to accept
+a title string:
+</p>
+
+<pre>func viewHandler(w http.ResponseWriter, r *http.Request, title string)
+func editHandler(w http.ResponseWriter, r *http.Request, title string)
+func saveHandler(w http.ResponseWriter, r *http.Request, title string)
+</pre>
+
+<p>
+Now let's define a wrapper function that <i>takes a function of the above
+type</i>, and returns a function of type <code>http.HandlerFunc</code>
+(suitable to be passed to the function <code>http.HandleFunc</code>):
+</p>
+
+<pre>func makeHandler(fn func (http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		// Here we will extract the page title from the Request,
+		// and call the provided handler 'fn'
+	}
+}
+</pre>
+
+<p>
+The returned function is called a closure because it encloses values defined
+outside of it. In this case, the variable <code>fn</code> (the single argument
+to <code>makeHandler</code>) is enclosed by the closure. The variable
+<code>fn</code> will be one of our save, edit, or view handlers.
+</p>
+
+<p>
+Now we can take the code from <code>getTitle</code> and use it here
+(with some minor modifications):
+</p>
+
+<pre>func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		title := r.URL.Path[lenPath:]
+		if !titleValidator.MatchString(title) {
+			http.NotFound(w, r)
+			return
+		}
+		fn(w, r, title)
+	}
+}
+</pre>
+
+<p>
+The closure returned by <code>makeHandler</code> is a function that takes
+an <code>http.ResponseWriter</code> and <code>http.Request</code> (in other
+words, an <code>http.HandlerFunc</code>). 
+The closure extracts the <code>title</code> from the request path, and
+validates it with the <code>TitleValidator</code> regexp. If the
+<code>title</code> is invalid, an error will be written to the
+<code>ResponseWriter</code> using the <code>http.NotFound</code> function. 
+If the <code>title</code> is valid, the enclosed handler function
+<code>fn</code> will be called with the <code>ResponseWriter</code>,
+<code>Request</code>, and <code>title</code> as arguments.
+</p>
+
+<p>
+Now we can wrap the handler functions with <code>makeHandler</code> in 
+<code>main</code>, before they are registered with the <code>http</code> 
+package:
+</p>
+
+<pre>func main() {
+	http.HandleFunc("/view/", makeHandler(viewHandler))
+	http.HandleFunc("/edit/", makeHandler(editHandler))
+	http.HandleFunc("/save/", makeHandler(saveHandler))
+	http.ListenAndServe(":8080", nil)
+}
+</pre>
+
+<p>
+Finally we remove the calls to <code>getTitle</code> from the handler functions,
+making them much simpler:
+</p>
+
+<pre>func viewHandler(w http.ResponseWriter, r *http.Request, title string) {
+	p, err := loadPage(title)
+	if err != nil {
+		http.Redirect(w, r, "/edit/"+title, http.StatusFound)
+		return
+	}
+	renderTemplate(w, "view", p)
+}
+
+func editHandler(w http.ResponseWriter, r *http.Request, title string) {
+	p, err := loadPage(title)
+	if err != nil {
+		p = &amp;Page{Title: title}
+	}
+	renderTemplate(w, "edit", p)
+}
+
+func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
+	body := r.FormValue("body")
+	p := &amp;Page{Title: title, Body: []byte(body)}
+	err := p.save()
+	if err != nil {
+		http.Error(w, err.String(), http.StatusInternalServerError)
+		return
+	}
+	http.Redirect(w, r, "/view/"+title, http.StatusFound)
+}
+</pre>
+
+<h2 id="tmp_290">Try it out!<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></h2>
+
+<p>
+<a href="http://golang.org/doc/codelab/wiki/final.go">Click here to view the final code listing.</a>
+</p>
+
+<p>
+Recompile the code, and run the app:
+</p>
+
+<pre>$ 8g wiki.go
+$ 8l wiki.8
+$ ./8.out
+</pre>
+
+<p>
+Visiting <a href="http://localhost:8080/view/ANewPage">http://localhost:8080/view/ANewPage</a>
+should present you with the page edit form. You should then be able to 
+enter some text, click 'Save', and be redirected to the newly created page.
+</p>
+
+<h2 id="tmp_300">Other tasks<span class="navtop"><a href="http://golang.org/doc/codelab/wiki/#top">[Top]</a></span></h2>
+
+<p>
+Here are some simple tasks you might want to tackle on your own:
+</p>
+
+<ul>
+<li>Store templates in <code>tmpl/</code> and page data in <code>data/</code>.
+</li><li>Add a handler to make the web root redirect to 
+	<code>/view/FrontPage</code>.</li>
+<li>Spruce up the page templates by making them valid HTML and adding some
+	CSS rules.</li>
+<li>Implement inter-page linking by converting instances of 
+	<code>[PageName]</code> to <br>
+	<code>&lt;a href="/view/PageName"&gt;PageName&lt;/a&gt;</code>.
+	(hint: you could use <code>regexp.ReplaceAllFunc</code> to do this)
+	</li>
+</ul>
+
+  </div>
+  <div id="site-info">
+    <p>release.r58.1 8699. Except as noted, this content is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/3.0/">Creative Commons Attribution 3.0 License</a>.</p>
+  </div>
+</div>
+<script type="text/javascript">
+(function() {
+  var ga = document.createElement("script"); ga.type = "text/javascript"; ga.async = true;
+  ga.src = ("https:" == document.location.protocol ? "https://ssl" : "http://www") + ".google-analytics.com/ga.js";
+  var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ga, s);
+})();
+</script>
+
+
+<!-- generated at Wed Jul 13 14:07:27 EST 2011 -->
+</body></html>

+ 205 - 0
eBook/examples/chapter_15/Codelab Writing Web Applications - The Go Programming Language_files/all.css

@@ -0,0 +1,205 @@
+/* General Styles */
+body {
+  font-family: "Bitstream Vera Sans", Verdana, sans-serif;
+  font-size: 81.25%;
+  line-height: 1.23em;
+  padding: 0;
+  margin: 1.23em;
+  background: white;
+  color: black;
+}
+a {
+  color: #04a;
+  text-decoration: none;
+}
+a:visited {
+  color: #04a;
+}
+a:hover {
+  color: #a40;
+  text-decoration: underline;
+}
+a:active {
+  color: #c00;
+}
+code, pre {
+  font-size: 1.2em; 
+}
+pre {
+  background: #F0F0F0;
+  padding: 0.5em 1em;
+}
+
+/* Top bar */
+#container {
+  width: 100%;
+  margin: auto;
+}
+#topnav {
+  height: 55px;
+  background: url(/doc/logo.png) no-repeat top left;
+}
+a#logo-box {
+  display: block;
+  height: 55px;
+}
+h1#title {
+  display: none;
+}
+#nav-main {
+  float: right;
+  width: 500px;
+  margin-top: -5px;
+  text-align: center;
+}
+#nav-main ul {
+  padding-left: 0;
+  margin-left: 0;
+  margin-bottom: 0.5em;
+}
+#nav-main li a {
+  display: inline;
+  display: inline-block;
+  padding: .46em .62em .38em .62em;
+}
+#nav-main li a:link,
+#nav-main li a:visited {
+  color: #000;
+}
+#nav-main li {
+  display: inline;
+  display: inline-block;
+  background: #e6e6e6 url(/doc/button_background.png) repeat-x;
+  border: solid 1px #999;
+  margin-left: -1px;
+  text-shadow: #fff 0 1px 0;
+  box-shadow: 0 1px 1px #ccc;
+  -moz-box-shadow: 0 1px 1px #ccc;
+  -webkit-box-shadow: 0 1px 1px #ccc;
+}
+#nav-main li:first-child {
+  -moz-border-top-left-radius: 4px;
+  border-top-left-radius: 4px;
+  -moz-border-bottom-left-radius: 4px;
+  border-bottom-left-radius: 4px;
+}
+#nav-main li:last-child {
+  -moz-border-top-right-radius: 4px;
+  border-top-right-radius: 4px;
+  -moz-border-bottom-right-radius: 4px;
+  border-bottom-right-radius: 4px;
+}
+#nav-main .quickref {
+  color: #444;
+}
+#nav-main .quickref .sep {
+  color: #999;
+}
+#search {
+  width: 120px;
+  margin-left: 0.5em;
+}
+#search.inactive {
+  text-align: center;
+  color: #444;
+}
+
+/* Footer */
+#site-info {
+  position: relative;
+  text-align: center;
+}
+#site-info, #site-info a:link, #site-info a:visited {
+  color: #aaa;
+}
+
+/* Content */
+#content {
+  clear: both;
+  padding: 0;
+  position: relative;
+  margin-top: 1.5em;
+  margin-bottom: 1.5em;
+  border-top: solid 1px #aaa;
+  border-bottom: solid 1px #aaa;
+}
+.left-column {
+  width: 49%;
+  float: left;
+}
+.right-column {
+  width: 49%;
+  float: right;
+}
+.end-columns {
+  clear: both;
+}
+#content h1 {
+  margin-bottom: -0em;
+  padding: 0;
+}
+#content h2 {
+  border-top: 2px solid #ddd;
+  padding: 8px 0;
+  margin: 1.5em 0 0;
+}
+#content .subtitle {
+  margin-top: 1em;
+  display: block;
+}
+.navtop a {
+  font-weight: normal; font-size: 7pt; 
+  float: right; color: #999;
+}
+
+/* Content and Code Highlighting */
+pre.ebnf, pre.grammar {
+  background: #FFFFE0;
+}
+span.ln {
+  font-size: 80%;
+  color: #777777;
+}
+span.comment {
+  color: #002090;
+}
+span.highlight {
+  background: #FF9900;
+  font-weight: bold;
+}
+span.highlight-comment {
+  background: #FF9900;
+  font-weight: bold;
+  color: #002090;
+}
+span.selection {
+  background: #FFFF00
+}
+span.selection-comment {
+  color: #002090;
+  background: #FFFF00
+}
+span.selection-highlight {
+  background: #FF9900;
+  font-weight: bold;
+}
+span.selection-highlight-comment {
+  background: #FF9900;
+  font-weight: bold;
+  color: #002090;
+}
+span.alert {
+  color: #D00000;
+}
+#nav table {
+  width: 100%;
+}
+.detail {
+  padding: 0.25em 1em;
+  background: #F4F4F4;
+}
+sup.new {
+  color: red;
+  font-size: 8px;
+  line-height: 0;
+}

+ 47 - 0
eBook/examples/chapter_15/Codelab Writing Web Applications - The Go Programming Language_files/ga.js

@@ -0,0 +1,47 @@
+(function(){var g=void 0,h=null,aa=encodeURIComponent,ba=decodeURIComponent,i=Math;function ca(a,b){return a.name=b}var k="push",da="load",l="charAt",ea="value",m="indexOf",fa="match",ga="name",ha="host",o="toString",r="length",s="prototype",t="split",u="stopPropagation",ia="scope",v="location",w="getString",x="substring",ja="navigator",y="join",z="toLowerCase",A;function ka(a,b){switch(b){case 0:return""+a;case 1:return a*1;case 2:return!!a;case 3:return a*1E3}return a}function B(a){return g==a||"-"==a||""==a}function la(a){if(!a||""==a)return"";for(;a&&" \n\r\t"[m](a[l](0))>-1;)a=a[x](1);for(;a&&" \n\r\t"[m](a[l](a[r]-1))>-1;)a=a[x](0,a[r]-1);return a}function ma(a){var b=1,c=0,d;if(!B(a)){b=0;for(d=a[r]-1;d>=0;d--)c=a.charCodeAt(d),b=(b<<6&268435455)+c+(c<<14),c=b&266338304,b=c!=0?b^c>>21:b}return b}function na(){return i.round(i.random()*2147483647)}
+function oa(){}function C(a,b){return aa instanceof Function?b?encodeURI(a):aa(a):(D(68),escape(a))}function E(a){a=a[t]("+")[y](" ");if(ba instanceof Function)try{return ba(a)}catch(b){D(17)}else D(68);return unescape(a)}var pa=function(a,b,c,d){a.addEventListener?a.addEventListener(b,c,!!d):a.attachEvent&&a.attachEvent("on"+b,c)},qa=function(a,b,c,d){a.removeEventListener?a.removeEventListener(b,c,!!d):a.detachEvent&&a.detachEvent("on"+b,c)};function ra(a){return a&&a[r]>0?a[0]:""}
+function sa(a){var b=a?a[r]:0;return b>0?a[b-1]:""}var ta=function(){this.prefix="ga.";this.F={}};ta[s].set=function(a,b){this.F[this.prefix+a]=b};ta[s].get=function(a){return this.F[this.prefix+a]};ta[s].contains=function(a){return this.get(a)!==g};function ua(a){a[m]("www.")==0&&(a=a[x](4));return a[z]()}function va(a,b){var c,d={url:a,protocol:"http",host:"",path:"",c:new ta,anchor:""};if(!a)return d;c=a[m]("://");if(c>=0)d.protocol=a[x](0,c),a=a[x](c+3);c=a.search("/|\\?|#");if(c>=0)d.host=a[x](0,c)[z](),a=a[x](c);else return d.host=a[z](),d;c=a[m]("#");if(c>=0)d.anchor=a[x](c+1),a=a[x](0,c);c=a[m]("?");c>=0&&(wa(d.c,a[x](c+1)),a=a[x](0,c));d.anchor&&b&&wa(d.c,d.anchor);a&&a[l](0)=="/"&&(a=a[x](1));d.path=a;return d}
+function wa(a,b){function c(b,c){a.contains(b)||a.set(b,[]);a.get(b)[k](c)}for(var d=la(b)[t]("&"),e=0;e<d[r];e++)if(d[e]){var f=d[e][m]("=");f<0?c(d[e],"1"):c(d[e][x](0,f),d[e][x](f+1))}}function xa(a,b){if(B(a))return"-";if("["==a[l](0)&&"]"==a[l](a[r]-1))return"-";var c=F.domain;c+=b&&b!="/"?b:"";return a[m](c)==(a[m]("http://")==0?7:a[m]("https://")==0?8:0)?"0":a};var ya=0;function G(a){return(a?"_":"")+ya++}var za=G(),Aa=G(),Ba=G(),H=G(),J=G(),K=G(),L=G(),Ca=G(),Da=G(),Ea=G(),Fa=G(),Ga=G(),Ha=G(),Ia=G(),Ja=G(),Ka=G(),La=G(),Ma=G(),Na=G(),Oa=G(),Pa=G(),Qa=G(),Ra=G(),Sa=G(),Ta=G(),Ua=G(),Va=G(),Wa=G(),Xa=G(),Ya=G(),Za=G(),$a=G(),ab=G(),bb=G(),cb=G();G();
+var M=G(!0),db=G(),eb=G(),fb=G(),gb=G(),hb=G(),ib=G(),jb=G(),kb=G(),lb=G(),mb=G(),N=G(!0),nb=G(!0),ob=G(!0),rb=G(!0),sb=G(!0),tb=G(!0),ub=G(!0),vb=G(!0),wb=G(!0),xb=G(!0),yb=G(!0),O=G(!0),zb=G(!0),Ab=G(!0),Bb=G(!0),Cb=G(!0),Db=G(!0),Eb=G(!0),Fb=G(!0),Gb=G(!0),Hb=G(!0),Ib=G(!0),Jb=G(!0),Kb=G(!0),Lb=G(!0),Mb=G(),Nb=G();G();var Ob=G(),Pb=G(),Qb=G(),Tb=G(),Ub=G(),Vb=G(),Wb=G(),Xb=G();G();var Yb=G(),Zb=G();var $b=function(){function a(a,c,d){P(Q[s],a,c,d)}R("_getName",Ba,58);R("_getAccount",za,64);R("_visitCode",N,54);R("_getClientInfo",Ia,53,1);R("_getDetectTitle",La,56,1);R("_getDetectFlash",Ja,65,1);R("_getLocalGifPath",Va,57);R("_getServiceMode",Wa,59);S("_setClientInfo",Ia,66,2);S("_setAccount",za,3);S("_setNamespace",Aa,48);S("_setAllowLinker",Fa,11,2);S("_setDetectFlash",Ja,61,2);S("_setDetectTitle",La,62,2);S("_setLocalGifPath",Va,46,0);S("_setLocalServerMode",Wa,92,g,0);S("_setRemoteServerMode",
+Wa,63,g,1);S("_setLocalRemoteServerMode",Wa,47,g,2);S("_setSampleRate",Ua,45,1);S("_setCampaignTrack",Ka,36,2);S("_setAllowAnchor",Ga,7,2);S("_setCampNameKey",Na,41);S("_setCampContentKey",Sa,38);S("_setCampIdKey",Ma,39);S("_setCampMediumKey",Qa,40);S("_setCampNOKey",Ta,42);S("_setCampSourceKey",Pa,43);S("_setCampTermKey",Ra,44);S("_setCampCIdKey",Oa,37);S("_setCookiePath",L,9,0);S("_setMaxCustomVariables",Xa,0,1);S("_setVisitorCookieTimeout",Ca,28,1);S("_setSessionCookieTimeout",Da,26,1);S("_setCampaignCookieTimeout",
+Ea,29,1);S("_setReferrerOverride",fb,49);a("_trackPageview",Q[s].ka,1);a("_trackEvent",Q[s].t,4);a("_trackSocial",Q[s].la,104);a("_trackPageLoadTime",Q[s].ja,100);a("_trackTrans",Q[s].ma,18);a("_sendXEvent",Q[s].s,78);a("_createEventTracker",Q[s].S,74);a("_getVersion",Q[s].X,60);a("_setDomainName",Q[s].r,6);a("_setAllowHash",Q[s].ba,8);a("_getLinkerUrl",Q[s].W,52);a("_link",Q[s].link,101);a("_linkByPost",Q[s].aa,102);a("_setTrans",Q[s].ea,20);a("_addTrans",Q[s].L,21);a("_addItem",Q[s].J,19);a("_setTransactionDelim",
+Q[s].fa,82);a("_setCustomVar",Q[s].ca,10);a("_deleteCustomVar",Q[s].U,35);a("_getVisitorCustomVar",Q[s].Y,50);a("_setXKey",Q[s].ha,83);a("_setXValue",Q[s].ia,84);a("_getXKey",Q[s].Z,76);a("_getXValue",Q[s].$,77);a("_clearXKey",Q[s].P,72);a("_clearXValue",Q[s].Q,73);a("_createXObj",Q[s].T,75);a("_addIgnoredOrganic",Q[s].H,15);a("_clearIgnoredOrganic",Q[s].M,97);a("_addIgnoredRef",Q[s].I,31);a("_clearIgnoredRef",Q[s].N,32);a("_addOrganic",Q[s].K,14);a("_clearOrganic",Q[s].O,70);a("_cookiePathCopy",
+Q[s].R,30);a("_get",Q[s].V,106);a("_set",Q[s].da,107);a("_addEventListener",Q[s].addEventListener,108);a("_removeEventListener",Q[s].removeEventListener,109);a("_initData",Q[s].l,2);a("_setVar",Q[s].ga,22);S("_setSessionTimeout",Da,27,3);S("_setCookieTimeout",Ea,25,3);S("_setCookiePersistence",Ca,24,1);a("_setAutoTrackOutbound",oa,79);a("_setTrackOutboundSubdomains",oa,81);a("_setHrefExamineLimit",oa,80)},P=function(a,b,c,d){a[b]=function(){D(d);return c.apply(this,arguments)}},R=function(a,b,c,d){Q[s][a]=
+function(){D(c);return ka(this.a.get(b),d)}},S=function(a,b,c,d,e){Q[s][a]=function(a){D(c);e==g?this.a.set(b,ka(a,d)):this.a.set(b,e)}},ac=function(a,b){return{type:b,target:a,stopPropagation:function(){throw"aborted";}}};var bc=function(a,b){return b!=="/"?!1:(a[m]("www.google.")==0||a[m](".google.")==0||a[m]("google.")==0)&&!(a[m]("google.org")>-1)?!0:!1},cc=function(a){var b=a.get(J),c=a[w](L,"/");bc(b,c)&&a[u]()};var gc=function(){var a={},b={},c=new dc;this.h=function(a,b){c.add(a,b)};var d=new dc;this.d=function(a,b){d.add(a,b)};var e=!1,f=!1,j=!0;this.G=function(){e=!0};this.f=function(a){this[da]();this.set(Mb,a,!0);e=!1;d.execute(this);e=!0;b={};this.i()};this.load=function(){e&&(e=!1,this.na(),ec(this),f||(f=!0,c.execute(this),fc(this),ec(this)),e=!0)};this.i=function(){if(e)if(f)e=!1,fc(this),e=!0;else this[da]()};this.get=function(c){c&&c[l](0)=="_"&&this[da]();return b[c]!==g?b[c]:a[c]};this.set=
+function(c,d,e){c&&c[l](0)=="_"&&this[da]();e?b[c]=d:a[c]=d;c&&c[l](0)=="_"&&this.i()};this.m=function(b){a[b]=this.b(b,0)+1};this.b=function(a,b){var c=this.get(a);return c==g||c===""?b:c*1};this.getString=function(a,b){var c=this.get(a);return c==g?b:c+""};this.na=function(){if(j){var b=this[w](J,""),c=this[w](L,"/");bc(b,c)||(a[K]=a[Ha]&&b!=""?ma(b):1,j=!1)}}};gc[s].stopPropagation=function(){throw"aborted";};function T(a,b){for(var b=b||[],c=0;c<b[r];c++){var d=b[c];if(""+a==d||d[m](a+".")==0)return d}return"-"}
+var hc=function(a,b){var c=a.b(K,1),d=b[t](".");if(d[r]!==6||d[0]!=c)return!1;var c=d[1]*1,e=d[2]*1,f=d[3]*1,j=d[4]*1,d=d[5]*1;if(!(c>=0&&e>0&&f>0&&j>0&&d>=0))return D(110),!1;a.set(N,c);a.set(sb,e);a.set(tb,f);a.set(ub,j);a.set(vb,d);return!0},ic=function(a){var b=a.get(N),c=a.get(sb),d=a.get(tb),e=a.get(ub),f=a.b(vb,1);b==g?D(113):b==NaN&&D(114);b>=0&&c>0&&d>0&&e>0&&f>=0||D(115);return[a.b(K,1),b!=g?b:"-",c||"-",d||"-",e||"-",f][y](".")},jc=function(a){return[a.b(K,1),a.b(yb,0),a.b(O,1),a.b(zb,
+0)][y](".")},kc=function(a,b){var c=b[t]("."),d=a.b(K,1);if(c[r]!==4||c[0]!=d)c=h;a.set(yb,c?c[1]*1:0);a.set(O,c?c[2]*1:10);a.set(zb,c?c[3]*1:a.get(H));return c!=h||b==d},lc=function(a,b){var c=C(a[w](ob,"")),d=[],e=a.get(M);if(!b&&e){for(var f=0;f<e[r];f++){var j=e[f];j&&j[ia]==1&&d[k](f+"="+C(j[ga])+"="+C(j[ea])+"=1")}d[r]>0&&(c+="|"+d[y](","))}return c?a.b(K,1)+"."+c:h},mc=function(a,b){var c=a.b(K,1),d=b[t](".");if(d[r]<2||d[0]!=c)return!1;c=d.slice(1)[y](".")[t]("|");c[r]>0&&a.set(ob,E(c[0]));
+if(c[r]<=1)return!0;for(var d=c[1][t](","),e=0;e<d[r];e++){var f=d[e][t]("=");if(f[r]==4){var j={};ca(j,E(f[1]));j.value=E(f[2]);j.scope=1;a.get(M)[f[0]]=j}}c[1][m]("^")>=0&&D(125);return!0},oc=function(a,b){var c=nc(a,b);return c?[a.b(K,1),a.b(Ab,0),a.b(Bb,1),a.b(Cb,1),c][y]("."):""},nc=function(a){function b(b,e){if(!B(a.get(b))){var f=a[w](b,""),f=f[t](" ")[y]("%20"),f=f[t]("+")[y]("%20");c[k](e+"="+f)}}var c=[];b(Eb,"utmcid");b(Ib,"utmcsr");b(Gb,"utmgclid");b(Hb,"utmdclid");b(Fb,"utmccn");b(Jb,
+"utmcmd");b(Kb,"utmctr");b(Lb,"utmcct");return c[y]("|")},qc=function(a,b){var c=a.b(K,1),d=b[t](".");if(d[r]<5||d[0]!=c)return a.set(Ab,g),a.set(Bb,g),a.set(Cb,g),a.set(Eb,g),a.set(Fb,g),a.set(Ib,g),a.set(Jb,g),a.set(Kb,g),a.set(Lb,g),a.set(Gb,g),a.set(Hb,g),!1;a.set(Ab,d[1]*1);a.set(Bb,d[2]*1);a.set(Cb,d[3]*1);pc(a,d.slice(4)[y]("."));return!0},pc=function(a,b){function c(a){return(a=b[fa](a+"=(.*?)(?:\\|utm|$)"))&&a[r]==2?a[1]:g}function d(b,c){c&&(c=e?E(c):c[t]("%20")[y](" "),a.set(b,c))}b[m]("=")==
+-1&&(b=E(b));var e=c("utmcvr")=="2";d(Eb,c("utmcid"));d(Fb,c("utmccn"));d(Ib,c("utmcsr"));d(Jb,c("utmcmd"));d(Kb,c("utmctr"));d(Lb,c("utmcct"));d(Gb,c("utmgclid"));d(Hb,c("utmdclid"))};var dc=function(){this.q=[]};dc[s].add=function(a,b){this.q[k]({name:a,ua:b})};dc[s].execute=function(a){try{for(var b=0;b<this.q[r];b++)this.q[b].ua.call(U,a)}catch(c){}};function rc(a){a.get(Ua)!=100&&a.get(N)%1E4>=a.get(Ua)*100&&a[u]()}function sc(a){tc()&&a[u]()}function uc(a){F[v].protocol=="file:"&&a[u]()}function vc(a){a.get(eb)||a.set(eb,F.title,!0);a.get(db)||a.set(db,F[v].pathname+F[v].search,!0)};var wc=new function(){var a=[];this.set=function(b){a[b]=!0};this.va=function(){for(var b=[],c=0;c<a[r];c++)a[c]&&(b[i.floor(c/6)]^=1<<c%6);for(c=0;c<b[r];c++)b[c]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"[l](b[c]||0);return b[y]("")+"~"}};function D(a){wc.set(a)};var U=window,F=document,tc=function(){var a=U._gaUserPrefs;return a&&a.ioo&&a.ioo()},xc=function(a,b){setTimeout(a,b)},V=function(a){for(var b=[],c=F.cookie[t](";"),a=RegExp("^\\s*"+a+"=\\s*(.*?)\\s*$"),d=0;d<c[r];d++){var e=c[d][fa](a);e&&b[k](e[1])}return b},X=function(a,b,c,d,e){var f;f=tc()?!1:bc(d,c)?!1:!0;if(f){if(b&&U[ja].userAgent[m]("Firefox")>=0){b=b.replace(/\n|\r/g," ");f=0;for(var j=b[r];f<j;++f){var p=b.charCodeAt(f)&255;if(p==10||p==13)b=b[x](0,f)+"?"+b[x](f+1)}}b&&b[r]>2E3&&(b=b[x](0,
+2E3),D(69));a=a+"="+b+"; path="+c+"; ";e&&(a+="expires="+(new Date((new Date).getTime()+e)).toGMTString()+"; ");d&&(a+="domain="+d+";");F.cookie=a}};var yc,zc,Ac=function(){if(!yc){var a={},b=U[ja],c=U.screen;a.C=c?c.width+"x"+c.height:"-";a.B=c?c.colorDepth+"-bit":"-";a.language=(b&&(b.language||b.browserLanguage)||"-")[z]();a.javaEnabled=b&&b.javaEnabled()?1:0;a.characterSet=F.characterSet||F.charset||"-";yc=a}},Bc=function(){Ac();for(var a=yc,b=U[ja],a=b.appName+b.version+a.language+b.platform+b.userAgent+a.javaEnabled+a.C+a.B+(F.cookie?F.cookie:"")+(F.referrer?F.referrer:""),b=a[r],c=U.history[r];c>0;)a+=c--^b++;return ma(a)},Cc=function(a){Ac();
+var b=yc;a.set(hb,b.C);a.set(ib,b.B);a.set(lb,b.language);a.set(mb,b.characterSet);a.set(jb,b.javaEnabled);if(a.get(Ia)&&a.get(Ja)){if(!(b=zc)){var c,d,e;d="ShockwaveFlash";if((b=(b=U[ja])?b.plugins:g)&&b[r]>0)for(c=0;c<b[r]&&!e;c++)d=b[c],d[ga][m]("Shockwave Flash")>-1&&(e=d.description[t]("Shockwave Flash ")[1]);else{d=d+"."+d;try{c=new ActiveXObject(d+".7"),e=c.GetVariable("$version")}catch(f){}if(!e)try{c=new ActiveXObject(d+".6"),e="WIN 6,0,21,0",c.AllowScriptAccess="always",e=c.GetVariable("$version")}catch(j){}if(!e)try{c=
+new ActiveXObject(d),e=c.GetVariable("$version")}catch(p){}e&&(e=e[t](" ")[1][t](","),e=e[0]+"."+e[1]+" r"+e[2])}b=e?e:"-"}zc=b;a.set(kb,zc)}else a.set(kb,"-")};var Y=function(){P(Y[s],"push",Y[s][k],5);P(Y[s],"_createAsyncTracker",Y[s].wa,33);P(Y[s],"_getAsyncTracker",Y[s].xa,34)};Y[s].wa=function(a,b){return Z.k(a,b||"")};Y[s].xa=function(a){return Z.p(a)};Y[s].push=function(a){for(var b=arguments,c=0,d=0;d<b[r];d++)try{if(typeof b[d]==="function")b[d]();else{var e="",f=b[d][0],j=f.lastIndexOf(".");j>0&&(e=f[x](0,j),f=f[x](j+1));var p=e=="_gat"?Z:e=="_gaq"?Dc:Z.p(e);p[f].apply(p,b[d].slice(1))}}catch(n){c++}return c};var Gc=function(){function a(a,b,c,d){g==f[a]&&(f[a]={});g==f[a][b]&&(f[a][b]=[]);f[a][b][c]=d}function b(a,b,c){if(g!=f[a]&&g!=f[a][b])return f[a][b][c]}function c(a,b){if(g!=f[a]&&g!=f[a][b]){f[a][b]=g;var c=!0,d;for(d=0;d<j[r];d++)if(g!=f[a][j[d]]){c=!1;break}c&&(f[a]=g)}}function d(a){var b="",c=!1,d,e;for(d=0;d<j[r];d++)if(e=a[j[d]],g!=e){c&&(b+=j[d]);for(var c=[],f=g,W=g,W=0;W<e[r];W++)if(g!=e[W]){f="";W!=kd&&g==e[W-1]&&(f+=W[o]()+pb);for(var Ic=e[W],Jc="",qb=g,Rb=g,Sb=g,qb=0;qb<Ic[r];qb++)Rb=
+Ic[l](qb),Sb=I[Rb],Jc+=g!=Sb?Sb:Rb;f+=Jc;c[k](f)}b+=p+c[y](q)+n;c=!1}else c=!0;return b}var e=this,f=[],j=["k","v"],p="(",n=")",q="*",pb="!",I={"'":"'0"};I[n]="'1";I[q]="'2";I[pb]="'3";var kd=1;e.qa=function(a){return g!=f[a]};e.n=function(){for(var a="",b=0;b<f[r];b++)g!=f[b]&&(a+=b[o]()+d(f[b]));return a};e.pa=function(a){if(a==g)return e.n();for(var b=a.n(),c=0;c<f[r];c++)g!=f[c]&&!a.qa(c)&&(b+=c[o]()+d(f[c]));return b};e.e=function(b,c,d){if(!Ec(d))return!1;a(b,"k",c,d);return!0};e.j=function(b,
+c,d){if(!Fc(d))return!1;a(b,"v",c,d[o]());return!0};e.w=function(a,c){return b(a,"k",c)};e.z=function(a,c){return b(a,"v",c)};e.u=function(a){c(a,"k")};e.v=function(a){c(a,"v")};P(e,"_setKey",e.e,89);P(e,"_setValue",e.j,90);P(e,"_getKey",e.w,87);P(e,"_getValue",e.z,88);P(e,"_clearKey",e.u,85);P(e,"_clearValue",e.v,86)};function Ec(a){return typeof a=="string"}function Fc(a){return typeof a!="number"&&(g==Number||!(a instanceof Number))||i.round(a)!=a||a==NaN||a==Infinity?!1:!0};var Hc=function(a){var b=U.gaGlobal;a&&!b&&(U.gaGlobal=b={});return b},Kc=function(){var a=Hc(!0).hid;if(a==h)a=na(),Hc(!0).hid=a;return a},Lc=function(a){a.set(gb,Kc());var b=Hc();if(b&&b.dh==a.get(K)){var c=b.sid;c&&(c=="0"&&D(112),a.set(ub,c),a.get(nb)&&a.set(tb,c));b=b.vid;a.get(nb)&&b&&(b=b[t]("."),b[1]*1||D(112),a.set(N,b[0]*1),a.set(sb,b[1]*1))}};var Mc,Nc=function(a,b,c){var d=a[w](J,""),e=a[w](L,"/"),a=a.b(Ca,0);X(b,c,e,d,a)},fc=function(a){var b=a[w](J,"");a.b(K,1);var c=a[w](L,"/");X("__utma",ic(a),c,b,a.get(Ca));X("__utmb",jc(a),c,b,a.get(Da));X("__utmc",""+a.b(K,1),c,b);var d=oc(a,!0);d?X("__utmz",d,c,b,a.get(Ea)):X("__utmz","",c,b,-1);(d=lc(a,!1))?X("__utmv",d,c,b,a.get(Ca)):X("__utmv","",c,b,-1)},ec=function(a){var b=a.b(K,1);if(!hc(a,T(b,V("__utma"))))return a.set(rb,!0),!1;var c=!kc(a,T(b,V("__utmb"))),d=T(b,V("__utmc"))!=a.b(K,
+1);d&&!c&&D(116);a.set(xb,c||d);qc(a,T(b,V("__utmz")));mc(a,T(b,V("__utmv")));Mc=!c;return!0},Oc=function(a){!Mc&&!(V("__utmb")[r]>0)&&(X("__utmd","1",a[w](L,"/"),a[w](J,""),1E4),V("__utmd")[r]==0&&a[u]())};var Qc=function(a){a.get(N)==g?Pc(a):a.get(rb)&&!a.get(Yb)?Pc(a):a.get(xb)&&(a.set(tb,a.get(ub)),a.set(ub,a.get(H)),a.m(vb),a.set(wb,!0),a.set(yb,0),a.set(O,10),a.set(zb,a.get(H)),a.set(xb,!1))},Pc=function(a){var b=a.get(H);a.set(nb,!0);a.set(N,na()^Bc(a)&2147483647);a.set(ob,"");a.set(sb,b);a.set(tb,b);a.set(ub,b);a.set(vb,1);a.set(wb,!0);a.set(yb,0);a.set(O,10);a.set(zb,b);a.set(M,[]);a.set(rb,!1);a.set(xb,!1)};var Rc="daum:q,eniro:search_word,naver:query,pchome:q,images.google:q,google:q,yahoo:p,yahoo:q,msn:q,bing:q,aol:query,aol:encquery,aol:q,lycos:query,ask:q,altavista:q,netscape:query,cnn:query,about:terms,mamma:q,alltheweb:q,voila:rdata,virgilio:qs,live:q,baidu:wd,alice:qs,yandex:text,najdi:q,mama:query,seznam:q,search:q,wp:szukaj,onet:qt,szukacz:q,yam:k,kvasir:q,sesam:q,ozu:q,terra:query,mynet:q,ekolay:q,rambler:query".split(","),Xc=function(a){if(a.get(Ka)&&!a.get(Yb)){for(var b=!B(a.get(Eb))||!B(a.get(Ib))||
+!B(a.get(Gb))||!B(a.get(Hb)),c={},d=0;d<Sc[r];d++){var e=Sc[d];c[e]=a.get(e)}d=va(F[v].href,a.get(Ga));if(!(sa(d.c.get(a.get(Ta)))=="1"&&b)&&(!Tc(a,d)&&!Uc(a)&&!b&&a.get(wb)&&a.get(wb)&&Vc(a,g,"(direct)",g,g,"(direct)","(none)",g,g),a.set(Db,Wc(a,c)),b=a.get(Ib)=="(direct)"&&a.get(Fb)=="(direct)"&&a.get(Jb)=="(none)",a.get(Db)||a.get(wb)&&!b))a.set(Ab,a.get(H)),a.set(Bb,a.get(vb)),a.m(Cb)}},Tc=function(a,b){function c(c,d){var d=d||"-",e=sa(b.c.get(a.get(c)));return e&&e!="-"?E(e):d}var d=sa(b.c.get(a.get(Ma)))||
+"-",e=sa(b.c.get(a.get(Pa)))||"-",f=sa(b.c.get(a.get(Oa)))||"-",j=sa(b.c.get("dclid"))||"-",p=c(Na,"(not set)"),n=c(Qa,"(not set)"),q=c(Ra),pb=c(Sa);if(B(d)&&B(f)&&B(j)&&B(e))return!1;if(B(q)){var I=xa(a.get(fb),a.get(L)),I=va(I,!0);(I=Yc(a,I))&&!B(I[1]&&!I[2])&&(q=I[1])}Vc(a,d,e,f,j,p,n,q,pb);return!0},Uc=function(a){var b=xa(a.get(fb),a.get(L)),c=va(b,!0);if(!(b!=g&&b!=h&&b!=""&&b!="0"&&b!="-"&&b[m]("://")>=0)||c&&c[ha][m]("google")>-1&&c.c.contains("q")&&c.path=="cse")return!1;if((b=Yc(a,c))&&
+!b[2])return Vc(a,g,b[0],g,g,"(organic)","organic",b[1],g),!0;else if(b)return!1;if(a.get(wb))a:{for(var b=a.get($a),d=ua(c[ha]),e=0;e<b[r];++e)if(d[m](b[e])>-1){a=!1;break a}Vc(a,g,d,g,g,"(referral)","referral",g,"/"+c.path);a=!0}else a=!1;return a},Yc=function(a,b){for(var c=a.get(Ya),d=0;d<c[r];++d){var e=c[d][t](":");if(b[ha][m](e[0][z]())>-1){var f=ra(b.c.get(e[1]));if(f){a:{for(var c=f,d=a.get(Za),c=E(c)[z](),j=0;j<d[r];++j)if(c==d[j]){c=!0;break a}c=!1}return[e[0],f,c]}}}return h},Vc=function(a,
+b,c,d,e,f,j,p,n){a.set(Eb,b);a.set(Ib,c);a.set(Gb,d);a.set(Hb,e);a.set(Fb,f);a.set(Jb,j);a.set(Kb,p);a.set(Lb,n)},Sc=[Fb,Eb,Gb,Hb,Ib,Jb,Kb,Lb],Wc=function(a,b){for(var c=0;c<Sc[r];c++){var d=Sc[c],e=b[d]||"-",d=a.get(d)||"-";if(e!=d)return!0}return!1};var $c=function(a){Zc(a,F[v].href)?(a.set(Yb,!0),D(12)):a.set(Yb,!1)},Zc=function(a,b){if(!a.get(Fa))return!1;var c=a.b(K,1),d=va(b,a.get(Ga)),e=T(c,d.c.get("__utma")),f=T(c,d.c.get("__utmb")),j=T(c,d.c.get("__utmc")),p=T(c,d.c.get("__utmx")),n=T(c,d.c.get("__utmz")),q=T(c,d.c.get("__utmv")),d=ra(d.c.get("__utmk"));if(ma(""+e+f+j+p+n+q)!=d)return!1;if(!hc(a,e))return e&&e[m](c+".")!=0&&D(126),!1;kc(a,f);a.b(K,1);qc(a,n);mc(a,q);c=E(p);e=a.b(K,1);f=c[t](".");f[r]<2||f[0]!=e||Nc(a,"__utmx",c);return!0},
+ad=function(a,b,c){var d;a.b(K,1);d=ic(a)||"-";var e=jc(a)||"-",f=""+a.b(K,1)||"-",j=ra(V("__utmx"))||"-",p=oc(a,!1)||"-",a=lc(a,!1)||"-",n=ma(""+d+e+f+j+p+a),q=[];q[k]("__utma="+d);q[k]("__utmb="+e);q[k]("__utmc="+f);q[k]("__utmx="+j);q[k]("__utmz="+p);q[k]("__utmv="+a);q[k]("__utmk="+n);d=q[y]("&");if(!d)return b;e=b[m]("#");return c?e<0?b+"#"+d:b+"&"+d:(c="",f=b[m]("?"),e>0&&(c=b[x](e),b=b[x](0,e)),f<0?b+"?"+d+c:b+"&"+d+c)};var bd="|",dd=function(a,b,c,d,e,f,j,p,n){var q=cd(a,b);q||(q={},a.get(ab)[k](q));q.id_=b;q.affiliation_=c;q.total_=d;q.tax_=e;q.shipping_=f;q.city_=j;q.state_=p;q.country_=n;q.items_=[];return q},ed=function(a,b,c,d,e,f,j){var a=cd(a,b)||dd(a,b,"",0,0,0,"","",""),p;a:{if(a&&a.items_){p=a.items_;for(var n=0;n<p[r];n++)if(p[n].sku_==c){p=p[n];break a}}p=h}n=p||{};n.transId_=b;n.sku_=c;n.name_=d;n.category_=e;n.price_=f;n.quantity_=j;p||a.items_[k](n);return n},cd=function(a,b){for(var c=a.get(ab),
+d=0;d<c[r];d++)if(c[d].id_==b)return c[d];return h};var fd,gd=function(a){var f;var e;if(!fd){var b;b=F[v].hash;var c=U[ga],d=/^#?gaso=([^&]*)/;if(f=(e=(b=b&&b[fa](d)||c&&c[fa](d))?b[1]:ra(V("GASO")),b=e)&&b[fa](/^(?:\|([-0-9a-z.]{1,30})\|)?([-.\w]{10,1200})$/i),c=f)if(Nc(a,"GASO",""+b),Z._gasoDomain=a.get(J),Z._gasoCPath=a.get(L),b="https://"+((c[1]||"www")+".google.com")+"/analytics/reporting/overlay_js?gaso="+c[2]+"&"+na())a=F.createElement("script"),a.type="text/javascript",a.async=!0,a.src=b,a.id="_gasojs",a.onload=g,b=F.getElementsByTagName("script")[0],
+b.parentNode.insertBefore(a,b);fd=!0}};var ld=function(a,b){if(a.b(N,0)%100>=a.b(Xb,0))return!1;var c=hd();c==g&&(c=id());if(c==g||c==Infinity||isNaN(c))return!1;c>0?b(jd(c)):pa(U,"load",function(){ld(a,b)},!1);return!0},jd=function(a){var b=new Gc,c=i.min(i.floor(a/100),5E3);b.e(14,1,c>0?c+"00":"0");b.j(14,1,a);return b},hd=function(){var a=U.performance||U.webkitPerformance;return(a=a&&a.timing)&&a.loadEventStart-a.fetchStart},id=function(){if(U.top==U){var a=U.external,b=a&&a.onloadT;a&&!a.isValidLoadTime&&(b=g);b>2147483648&&(b=g);
+b>0&&a.setPageReadyTime();return b}};var Q=function(a,b,c){function d(a){return function(b){if((b=b.get(Zb)[a])&&b[r])for(var c=ac(e,a),d=0;d<b[r];d++)b[d].call(e,c)}}var e=this;this.a=new gc;this.get=function(a){return this.a.get(a)};this.set=function(a,b,c){this.a.set(a,b,c)};this.set(za,b||"UA-XXXXX-X");this.set(Ba,a||"");this.set(Aa,c||"");this.set(H,i.round((new Date).getTime()/1E3));this.set(L,"/");this.set(Ca,63072E6);this.set(Ea,15768E6);this.set(Da,18E5);this.set(Fa,!1);this.set(Xa,50);this.set(Ga,!1);this.set(Ha,!0);this.set(Ia,
+!0);this.set(Ja,!0);this.set(Ka,!0);this.set(La,!0);this.set(Na,"utm_campaign");this.set(Ma,"utm_id");this.set(Oa,"gclid");this.set(Pa,"utm_source");this.set(Qa,"utm_medium");this.set(Ra,"utm_term");this.set(Sa,"utm_content");this.set(Ta,"utm_nooverride");this.set(Ua,100);this.set(Xb,10);this.set(Va,"/__utm.gif");this.set(Wa,1);this.set(ab,[]);this.set(M,[]);this.set(Ya,Rc);this.set(Za,[]);this.set($a,[]);this.r("auto");this.set(fb,F.referrer);this.set(Zb,{hit:[],load:[]});this.a.h("0",$c);this.a.h("1",
+Qc);this.a.h("2",Xc);this.a.h("4",d("load"));this.a.h("5",gd);this.a.d("A",sc);this.a.d("B",uc);this.a.d("C",Qc);this.a.d("D",rc);this.a.d("E",cc);this.a.d("F",md);this.a.d("G",Oc);this.a.d("H",vc);this.a.d("I",Cc);this.a.d("J",Lc);this.a.d("K",d("hit"));this.a.d("L",nd);this.a.d("M",od);this.get(H)===0&&D(111);this.a.G()};A=Q[s];A.g=function(){var a=this.get(bb);a||(a=new Gc,this.set(bb,a));return a};
+A.oa=function(a){for(var b in a){var c=a[b];a.hasOwnProperty(b)&&typeof c!="function"&&this.set(b,c,!0)}};A.ka=function(a){a&&a!=g&&(a.constructor+"")[m]("String")>-1?(D(13),this.set(db,a,!0)):typeof a==="object"&&a!==h&&this.oa(a);this.a.f("page")};A.t=function(a,b,c,d){if(a==""||!Ec(a)||b==""||!Ec(b))return!1;if(c!=g&&!Ec(c))return!1;if(d!=g&&!Fc(d))return!1;this.set(Ob,a,!0);this.set(Pb,b,!0);this.set(Qb,c,!0);this.set(Tb,d,!0);this.a.f("event");return!0};
+A.la=function(a,b,c,d){if(!a||!b)return!1;this.set(Ub,a[x](0,15),!0);this.set(Vb,b[x](0,15),!0);this.set(Wb,c||F[v].href,!0);d&&this.set(db,d,!0);this.a.f("social");return!0};A.ja=function(){var a=this;return ld(this.a,function(b){a.s(b)})};A.ma=function(){this.a.f("trans")};A.s=function(a){this.set(cb,a,!0);this.a.f("event")};A.S=function(a){this.l();var b=this;return{_trackEvent:function(c,d,e){D(91);b.t(a,c,d,e)}}};A.V=function(a){return this.get(a)};
+A.da=function(a,b){if(a)if(a!=g&&(a.constructor+"")[m]("String")>-1)this.set(a,b);else if(typeof a=="object")for(var c in a)a.hasOwnProperty(c)&&this.set(c,a[c])};A.addEventListener=function(a,b){var c=this.get(Zb)[a];c&&c[k](b)};A.removeEventListener=function(a,b){for(var c=this.get(Zb)[a],d=0;c&&d<c[r];d++)if(c[d]==b){c.splice(d,1);break}};A.X=function(){return"5.1.2"};A.r=function(a){this.get(Ha);a=a=="auto"?ua(F.domain):!a||a=="-"||a=="none"?"":a[z]();this.set(J,a)};
+A.ba=function(a){this.set(Ha,!!a)};A.W=function(a,b){return ad(this.a,a,b)};A.link=function(a,b){if(this.a.get(Fa)&&a){var c=ad(this.a,a,b);F[v].href=c}};A.aa=function(a,b){this.a.get(Fa)&&a&&a.action&&(a.action=ad(this.a,a.action,b))};
+A.ea=function(){this.l();var a=this.a,b=F.getElementById?F.getElementById("utmtrans"):F.utmform&&F.utmform.utmtrans?F.utmform.utmtrans:h;if(b&&b[ea]){a.set(ab,[]);for(var b=b[ea][t]("UTM:"),c=0;c<b[r];c++){b[c]=la(b[c]);for(var d=b[c][t](bd),e=0;e<d[r];e++)d[e]=la(d[e]);"T"==d[0]?dd(a,d[1],d[2],d[3],d[4],d[5],d[6],d[7],d[8]):"I"==d[0]&&ed(a,d[1],d[2],d[3],d[4],d[5],d[6])}}};A.L=function(a,b,c,d,e,f,j,p){return dd(this.a,a,b,c,d,e,f,j,p)};A.J=function(a,b,c,d,e,f){return ed(this.a,a,b,c,d,e,f)};
+A.fa=function(a){bd=a||"|"};A.ca=function(a,b,c,d){var e=this.a;if(a<=0||a>e.get(Xa))a=!1;else if(!b||!c||C(b)[r]+C(c)[r]>64)a=!1;else{d!=1&&d!=2&&(d=3);var f={};ca(f,b);f.value=c;f.scope=d;e.get(M)[a]=f;a=!0}a&&this.a.i();return a};A.U=function(a){this.a.get(M)[a]=g;this.a.i()};A.Y=function(a){return(a=this.a.get(M)[a])&&a[ia]==1?a[ea]:g};A.ha=function(a,b,c){this.g().e(a,b,c)};A.ia=function(a,b,c){this.g().j(a,b,c)};A.Z=function(a,b){return this.g().w(a,b)};
+A.$=function(a,b){return this.g().z(a,b)};A.P=function(a){this.g().u(a)};A.Q=function(a){this.g().v(a)};A.T=function(){return new Gc};A.H=function(a){a&&this.get(Za)[k](a[z]())};A.M=function(){this.set(Za,[])};A.I=function(a){a&&this.get($a)[k](a[z]())};A.N=function(){this.set($a,[])};A.K=function(a,b,c){if(a&&b){var d=this.get(Ya);d.splice(c?0:d[r],0,a+":"+b[z]())}};A.O=function(){this.set(Ya,[])};
+A.R=function(a){this.a[da]();var b=this.get(L),c=ra(V("__utmx"))||"";this.set(L,a);this.a.i();Nc(this.a,"__utmx",c);this.set(L,b)};A.l=function(){this.a[da]()};A.ga=function(a){a&&a!=""&&(this.set(ob,a),this.a.f("var"))};var md=function(a){a.get(Mb)!=="trans"&&a.b(yb,0)>=500&&a[u]();if(a.get(Mb)==="event"){var b=(new Date).getTime(),c=a.b(zb,0),d=a.b(ub,0),c=i.floor(0.2*((b-(c!=d?c:c*1E3))/1E3));c>0&&(a.set(zb,b),a.set(O,i.min(10,a.b(O,0)+c)));a.b(O,0)<=0&&a[u]()}},od=function(a){a.get(Mb)==="event"&&a.set(O,i.max(0,a.b(O,10)-1))};var pd=function(){var a=[];this.add=function(b,c,d){d&&(c=C(""+c));a[k](b+"="+c)};this.toString=function(){return a[y]("&")}},qd=function(a,b){(b||a.get(Wa)!=2)&&a.m(yb)},rd=function(a,b){b.add("utmwv","5.1.2");b.add("utms",a.get(yb));b.add("utmn",na());var c=F[v].hostname;B(c)||b.add("utmhn",c,!0);c=a.get(Ua);c!=100&&b.add("utmsp",c,!0)},td=function(a,b){b.add("utmac",a.get(za));sd(a,b);Z.o&&b.add("aip",1);b.add("utmu",wc.va())},sd=function(a,b){function c(a,b){b&&d[k](a+"="+b+";")}var d=[];c("__utma",
+ic(a));c("__utmz",oc(a,!1));c("__utmv",lc(a,!0));c("__utmx",ra(V("__utmx")));b.add("utmcc",d[y]("+"),!0)},ud=function(a,b){a.get(Ia)&&(b.add("utmcs",a.get(mb),!0),b.add("utmsr",a.get(hb)),b.add("utmsc",a.get(ib)),b.add("utmul",a.get(lb)),b.add("utmje",a.get(jb)),b.add("utmfl",a.get(kb),!0))},vd=function(a,b){a.get(La)&&a.get(eb)&&b.add("utmdt",a.get(eb),!0);b.add("utmhid",a.get(gb));b.add("utmr",xa(a.get(fb),a.get(L)),!0);b.add("utmp",C(a.get(db),!0),!0)},wd=function(a,b){for(var c=a.get(bb),d=a.get(cb),
+e=a.get(M)||[],f=0;f<e[r];f++){var j=e[f];j&&(c||(c=new Gc),c.e(8,f,j[ga]),c.e(9,f,j[ea]),j[ia]!=3&&c.e(11,f,""+j[ia]))}!B(a.get(Ob))&&!B(a.get(Pb))&&(c||(c=new Gc),c.e(5,1,a.get(Ob)),c.e(5,2,a.get(Pb)),e=a.get(Qb),e!=g&&c.e(5,3,e),e=a.get(Tb),e!=g&&c.j(5,1,e));c?b.add("utme",c.pa(d),!0):d&&b.add("utme",d.n(),!0)},xd=function(a,b,c){var d=new pd;qd(a,c);rd(a,d);d.add("utmt","tran");d.add("utmtid",b.id_,!0);d.add("utmtst",b.affiliation_,!0);d.add("utmtto",b.total_,!0);d.add("utmttx",b.tax_,!0);d.add("utmtsp",
+b.shipping_,!0);d.add("utmtci",b.city_,!0);d.add("utmtrg",b.state_,!0);d.add("utmtco",b.country_,!0);!c&&td(a,d);return d[o]()},yd=function(a,b,c){var d=new pd;qd(a,c);rd(a,d);d.add("utmt","item");d.add("utmtid",b.transId_,!0);d.add("utmipc",b.sku_,!0);d.add("utmipn",b.name_,!0);d.add("utmiva",b.category_,!0);d.add("utmipr",b.price_,!0);d.add("utmiqt",b.quantity_,!0);!c&&td(a,d);return d[o]()},zd=function(a,b){var c=a.get(Mb);if(c=="page")c=new pd,qd(a,b),rd(a,c),wd(a,c),ud(a,c),vd(a,c),!b&&td(a,
+c),c=[c[o]()];else if(c=="event")c=new pd,qd(a,b),rd(a,c),c.add("utmt","event"),wd(a,c),ud(a,c),vd(a,c),!b&&td(a,c),c=[c[o]()];else if(c=="var")c=new pd,qd(a,b),rd(a,c),c.add("utmt","var"),!b&&td(a,c),c=[c[o]()];else if(c=="trans")for(var c=[],d=a.get(ab),e=0;e<d[r];++e){c[k](xd(a,d[e],b));for(var f=d[e].items_,j=0;j<f[r];++j)c[k](yd(a,f[j],b))}else c=="social"?b?c=[]:(c=new pd,qd(a,b),rd(a,c),c.add("utmt","social"),c.add("utmsn",a.get(Ub),!0),c.add("utmsa",a.get(Vb),!0),c.add("utmsid",a.get(Wb),
+!0),wd(a,c),ud(a,c),vd(a,c),td(a,c),c=[c[o]()]):c=[];return c},nd=function(a){var b,c=a.get(Nb),d=a.get(Wa);if(d==0||d==2){var e=a.get(Va)+"?";b=zd(a,!0);for(var f=0,j=b[r];f<j;f++)Ad(b[f],d!=2&&f==j-1&&c,e,!0)}if(d==1||d==2){b=zd(a);f=0;for(j=b[r];f<j;f++)try{Ad(b[f],f==j-1&&c)}catch(p){var d=a,e=p,n=new pd;n.add("err",e[ga]);n.add("max",e.message);n.add("len",e.D);n.add("utmwv","5.1.2e");n.add("utmac",d.get(za));n.add("utmn",na());Z.o&&n.add("aip",1);Ad(n[o]())}}};var Bd="https:"==F[v].protocol?"https://ssl.google-analytics.com":"http://www.google-analytics.com",Cd=function(a){ca(this,"len");this.message=8192;this.D=a},Dd=function(a){ca(this,"ff2post");this.message=2036;this.D=a},Ad=function(a,b,c,d){b=b||oa;if(d||a[r]<=2036)Ed(a,b,c);else if(a[r]<=8192){if(U[ja].userAgent[m]("Firefox")>=0&&![].reduce)throw new Dd(a[r]);Fd(a,b)||Gd(a,b)}else throw new Cd(a[r]);},Ed=function(a,b,c){var c=c||Bd+"/__utm.gif?",d=new Image(1,1);d.src=c+a;d.onload=function(){d.onload=
+h;b()}},Fd=function(a,b){var c,d=Bd+"/p/__utm.gif",e=U.XDomainRequest;if(e)c=new e,c.open("POST",d);else if(e=U.XMLHttpRequest)e=new e,"withCredentials"in e&&(c=e,c.open("POST",d,!0),c.setRequestHeader("Content-Type","text/plain"));if(c)return c.onreadystatechange=function(){c.readyState==4&&(b(),c=h)},c.send(a),!0},Gd=function(a,b){if(F.body){a=aa(a);try{var c=F.createElement('<iframe name="'+a+'"></iframe>')}catch(d){c=F.createElement("iframe"),ca(c,a)}c.height="0";c.width="0";c.style.display="none";
+c.style.visibility="hidden";var e=F[v],e=Bd+"/u/post_iframe.html#"+aa(e.protocol+"//"+e[ha]+"/favicon.ico"),f=function(){c.src="";c.parentNode&&c.parentNode.removeChild(c)};pa(U,"beforeunload",f);var j=!1,p=0,n=function(){if(!j){try{if(p>9||c.contentWindow[v][ha]==F[v][ha]){j=!0;f();qa(U,"beforeunload",f);b();return}}catch(a){}p++;setTimeout(n,200)}};pa(c,"load",n);F.body.appendChild(c);c.src=e}else xc(function(){Gd(a,b)},100)};var $=function(){this.o=!1;this.A={};this.ra=0;this._gasoCPath=this._gasoDomain=g;P($[s],"_createTracker",$[s].k,55);P($[s],"_getTracker",$[s].ta,0);P($[s],"_getTrackerByName",$[s].p,51);P($[s],"_anonymizeIp",$[s].sa,16);$b()};$[s].ta=function(a,b){return this.k(a,g,b)};$[s].k=function(a,b,c){b&&D(23);c&&D(67);b==g&&(b="~"+Z.ra++);return Z.A[b]=new Q(b,a,c)};$[s].p=function(a){a=a||"";return Z.A[a]||Z.k(g,a)};$[s].sa=function(){this.o=!0};var Hd=function(a){if(F.webkitVisibilityState=="prerender")return!1;a();return!0};var Z=new $;var Id=U._gat;Id&&typeof Id._getTracker=="function"?Z=Id:U._gat=Z;var Dc=new Y;(function(a){if(!Hd(a)){D(123);var b=!1,c=function(){!b&&Hd(a)&&(D(124),b=!0,qa(F,"webkitvisibilitychange",c))};pa(F,"webkitvisibilitychange",c)}})(function(){var a=U._gaq,b=!1;if(a&&typeof a[k]=="function"&&(b=Object[s][o].call(Object(a))=="[object Array]",!b)){Dc=a;return}U._gaq=Dc;b&&Dc[k].apply(Dc,a)});})();

+ 190 - 0
eBook/examples/chapter_15/Codelab Writing Web Applications - The Go Programming Language_files/godocs.js

@@ -0,0 +1,190 @@
+// Except as noted, this content is licensed under Creative Commons
+// Attribution 3.0
+
+/* A little code to ease navigation of these documents.
+ *
+ * On window load we:
+ *  + Generate a table of contents (godocs_generateTOC)
+ *  + Add links up to the top of the doc from each section (godocs_addTopLinks)
+ */
+
+/* We want to do some stuff on page load (after the HTML is rendered).
+   So listen for that:
+ */
+function bindEvent(el, e, fn) {
+  if (el.addEventListener){
+    el.addEventListener(e, fn, false);
+  } else if (el.attachEvent){
+    el.attachEvent('on'+e, fn);
+  }
+}
+bindEvent(window, 'load', godocs_onload);
+
+function godocs_onload() {
+  godocs_bindSearchEvents();
+  godocs_generateTOC();
+  godocs_addTopLinks();
+}
+
+function godocs_bindSearchEvents() {
+  var search = document.getElementById('search');
+  if (!search) {
+    // no search box (index disabled)
+    return;
+  }
+  function clearInactive() {
+    if (search.className == "inactive") {
+      search.value = "";
+      search.className = "";
+    }
+  }
+  function restoreInactive() {
+    if (search.value != "") {
+      return;
+    }
+    if (search.type != "search") {
+      search.value = search.getAttribute("placeholder");
+    }
+    search.className = "inactive";
+  }
+  restoreInactive();
+  bindEvent(search, 'focus', clearInactive);
+  bindEvent(search, 'blur', restoreInactive);
+}
+
+/* Generates a table of contents: looks for h2 and h3 elements and generates
+ * links.  "Decorates" the element with id=="nav" with this table of contents.
+ */
+function godocs_generateTOC() {
+  var navbar = document.getElementById('nav');
+  if (!navbar) { return; }
+
+  var toc_items = [];
+
+  var i;
+  for (i = 0; i < navbar.parentNode.childNodes.length; i++) {
+    var node = navbar.parentNode.childNodes[i];
+    if ((node.tagName == 'h2') || (node.tagName == 'H2')) {
+      if (!node.id) {
+        node.id = 'tmp_' + i;
+      }
+      var text = godocs_nodeToText(node);
+      if (!text) { continue; }
+
+      var textNode = document.createTextNode(text);
+
+      var link = document.createElement('a');
+      link.href = '#' + node.id;
+      link.appendChild(textNode);
+
+      // Then create the item itself
+      var item = document.createElement('dt');
+
+      item.appendChild(link);
+      toc_items.push(item);
+    }
+    if ((node.tagName == 'h3') || (node.tagName == 'H3')) {
+      if (!node.id) {
+        node.id = 'tmp_' + i;
+      }
+      var text = godocs_nodeToText(node);
+      if (!text) { continue; }
+
+      var textNode = document.createTextNode(text);
+
+      var link = document.createElement('a');
+      link.href = '#' + node.id;
+      link.appendChild(textNode);
+
+      // Then create the item itself
+      var item = document.createElement('dd');
+
+      item.appendChild(link);
+      toc_items.push(item);
+    }
+  }
+
+  if (toc_items.length <= 1) { return; }
+
+  var dl1 = document.createElement('dl');
+  var dl2 = document.createElement('dl');
+
+  var split_index = (toc_items.length / 2) + 1;
+  if (split_index < 8) {
+    split_index = toc_items.length;
+  }
+
+  for (i = 0; i < split_index; i++) {
+    dl1.appendChild(toc_items[i]);
+  }
+  for (/* keep using i */; i < toc_items.length; i++) {
+    dl2.appendChild(toc_items[i]);
+  }
+
+  var tocTable = document.createElement('table');
+  navbar.appendChild(tocTable);
+  tocTable.className = 'unruled';
+  var tocBody = document.createElement('tbody');
+  tocTable.appendChild(tocBody);
+
+  var tocRow = document.createElement('tr');
+  tocBody.appendChild(tocRow);
+
+  // 1st column
+  var tocCell = document.createElement('td');
+  tocCell.className = 'first';
+  tocRow.appendChild(tocCell);
+  tocCell.appendChild(dl1);
+
+  // 2nd column
+  tocCell = document.createElement('td');
+  tocRow.appendChild(tocCell);
+  tocCell.appendChild(dl2);
+}
+
+/* Returns the "This sweet header" from <h2>This <i>sweet</i> header</h2>.
+ * Takes a node, returns a string.
+ */
+function godocs_nodeToText(node) {
+  var TEXT_NODE = 3; // Defined in Mozilla but not MSIE :(
+
+  var text = '';
+  for (var j = 0; j != node.childNodes.length; j++) {
+    var child = node.childNodes[j];
+    if (child.nodeType == TEXT_NODE) {
+      if (child.nodeValue != '[Top]') { //ok, that's a hack, but it works.
+        text = text + child.nodeValue;
+      }
+    } else {
+      text = text + godocs_nodeToText(child);
+    }
+  }
+  return text;
+}
+
+/* For each H2 heading, add a link up to the #top of the document.
+ * (As part of this: ensure existence of 'top' named anchor link
+ * (theoretically at doc's top).)
+ */
+function godocs_addTopLinks() {
+  /* Make sure there's a "top" to link to. */
+  var top = document.getElementById('top');
+  if (!top) {
+    document.body.id = 'top';
+  }
+
+  if (!document.getElementsByTagName) return; // no browser support
+
+  var headers = document.getElementsByTagName('h2');
+
+  for (var i = 0; i < headers.length; i++) {
+    var span = document.createElement('span');
+    span.className = 'navtop';
+    var link = document.createElement('a');
+    span.appendChild(link);
+    link.href = '#top';
+    var textNode = document.createTextNode('[Top]');
+    link.appendChild(textNode);
+    headers[i].appendChild(span);
+  }
+}

+ 36 - 0
eBook/examples/chapter_15/client.go

@@ -0,0 +1,36 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"net"
+	"bufio"
+	"strings"
+)
+
+func main() {
+	conn, err := net.Dial("tcp", "localhost:50000")
+	if err != nil {
+		// No connection could be made because the target machine actively refused it.
+		fmt.Println("Error dialing", err.Error())
+		return // terminate program
+	}
+
+	inputReader := bufio.NewReader(os.Stdin)
+	fmt.Println("First, what is your name?")
+	clientName, _ := inputReader.ReadString('\n')
+	// fmt.Printf("CLIENTNAME %s",clientName)
+	trimmedClient := strings.Trim(clientName, "\r\n") // "\r\n" on Windows, "\n" on Linux
+		
+	for {
+		fmt.Println("What to send to the server? Type Q to quit.")
+		input, _ := inputReader.ReadString('\n')
+		trimmedInput := strings.Trim(input, "\r\n")   
+		// fmt.Printf("input:--%s--",input)
+		// fmt.Printf("trimmedInput:--%s--",trimmedInput)
+		if trimmedInput == "Q" {
+			return
+		}
+		_, err = conn.Write([]byte(trimmedClient + " says: " + trimmedInput))
+	}
+}

binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/00changelog.i


+ 1 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/branch

@@ -0,0 +1 @@
+default

+ 2 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/cache/branchheads

@@ -0,0 +1,2 @@
+844fb91a777b63798d4657e1c40669e8968f79ad 4
+844fb91a777b63798d4657e1c40669e8968f79ad default

+ 2 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/cache/tags

@@ -0,0 +1,2 @@
+4 844fb91a777b63798d4657e1c40669e8968f79ad
+

binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/dirstate


+ 2 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/hgrc

@@ -0,0 +1,2 @@
+[paths]
+default = https://code.google.com/p/go.net

+ 4 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/requires

@@ -0,0 +1,4 @@
+revlogv1
+store
+fncache
+dotencode

binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/00changelog.i


binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/00manifest.i


binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/_a_u_t_h_o_r_s.i


binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/_c_o_n_t_r_i_b_u_t_o_r_s.i


binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/_l_i_c_e_n_s_e.i


binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/_r_e_a_d_m_e.i


binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/codereview.cfg.i


binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/dict/dict.go.i


binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/spdy/read.go.i


binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/spdy/spdy__test.go.i


binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/spdy/types.go.i


binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/spdy/write.go.i


binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/client.go.i


binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/hixie.go.i


binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/hixie__test.go.i


binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/hybi.go.i


binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/hybi__test.go.i


binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/server.go.i


binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/websocket.go.i


binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/websocket__test.go.i


binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/~2ehgignore.i


+ 19 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/fncache

@@ -0,0 +1,19 @@
+data/websocket/hybi.go.i
+data/AUTHORS.i
+data/CONTRIBUTORS.i
+data/spdy/read.go.i
+data/dict/dict.go.i
+data/codereview.cfg.i
+data/README.i
+data/websocket/websocket_test.go.i
+data/.hgignore.i
+data/websocket/hixie.go.i
+data/websocket/hixie_test.go.i
+data/websocket/websocket.go.i
+data/LICENSE.i
+data/spdy/types.go.i
+data/spdy/spdy_test.go.i
+data/websocket/client.go.i
+data/spdy/write.go.i
+data/websocket/hybi_test.go.i
+data/websocket/server.go.i

binární
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/undo


+ 0 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/undo.bookmarks


+ 1 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/undo.branch

@@ -0,0 +1 @@
+default

+ 3 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/undo.desc

@@ -0,0 +1,3 @@
+0
+pull
+https://code.google.com/p/go.net

+ 0 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/.hg/undo.dirstate


+ 2 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/.hgignore

@@ -0,0 +1,2 @@
+syntax:glob
+last-change

+ 3 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/AUTHORS

@@ -0,0 +1,3 @@
+# This source code refers to The Go Authors for copyright purposes.
+# The master list of authors is in the main Go distribution,
+# visible at http://tip.golang.org/AUTHORS.

+ 3 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/CONTRIBUTORS

@@ -0,0 +1,3 @@
+# This source code was written by the Go contributors.
+# The master list of contributors is in the main Go distribution,
+# visible at http://tip.golang.org/CONTRIBUTORS.

+ 27 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/LICENSE

@@ -0,0 +1,27 @@
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 3 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/README

@@ -0,0 +1,3 @@
+This repository holds supplementary Go networking libraries.
+
+To submit changes to this repository, see http://golang.org/doc/contribute.html.

+ 2 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/codereview.cfg

@@ -0,0 +1,2 @@
+defaultcc: [email protected]
+contributors: http://go.googlecode.com/hg/CONTRIBUTORS

+ 210 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/dict/dict.go

@@ -0,0 +1,210 @@
+// Copyright 2010 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package dict implements the Dictionary Server Protocol
+// as defined in RFC 2229.
+package dict
+
+import (
+	"net/textproto"
+	"strconv"
+	"strings"
+)
+
+// A Client represents a client connection to a dictionary server.
+type Client struct {
+	text *textproto.Conn
+}
+
+// Dial returns a new client connected to a dictionary server at
+// addr on the given network.
+func Dial(network, addr string) (*Client, error) {
+	text, err := textproto.Dial(network, addr)
+	if err != nil {
+		return nil, err
+	}
+	_, _, err = text.ReadCodeLine(220)
+	if err != nil {
+		text.Close()
+		return nil, err
+	}
+	return &Client{text: text}, nil
+}
+
+// Close closes the connection to the dictionary server.
+func (c *Client) Close() error {
+	return c.text.Close()
+}
+
+// A Dict represents a dictionary available on the server.
+type Dict struct {
+	Name string // short name of dictionary
+	Desc string // long description
+}
+
+// Dicts returns a list of the dictionaries available on the server.
+func (c *Client) Dicts() ([]Dict, error) {
+	id, err := c.text.Cmd("SHOW DB")
+	if err != nil {
+		return nil, err
+	}
+
+	c.text.StartResponse(id)
+	defer c.text.EndResponse(id)
+
+	_, _, err = c.text.ReadCodeLine(110)
+	if err != nil {
+		return nil, err
+	}
+	lines, err := c.text.ReadDotLines()
+	if err != nil {
+		return nil, err
+	}
+	_, _, err = c.text.ReadCodeLine(250)
+
+	dicts := make([]Dict, len(lines))
+	for i := range dicts {
+		d := &dicts[i]
+		a, _ := fields(lines[i])
+		if len(a) < 2 {
+			return nil, textproto.ProtocolError("invalid dictionary: " + lines[i])
+		}
+		d.Name = a[0]
+		d.Desc = a[1]
+	}
+	return dicts, err
+}
+
+// A Defn represents a definition.
+type Defn struct {
+	Dict Dict   // Dict where definition was found
+	Word string // Word being defined
+	Text []byte // Definition text, typically multiple lines
+}
+
+// Define requests the definition of the given word.
+// The argument dict names the dictionary to use,
+// the Name field of a Dict returned by Dicts.
+//
+// The special dictionary name "*" means to look in all the
+// server's dictionaries.
+// The special dictionary name "!" means to look in all the
+// server's dictionaries in turn, stopping after finding the word
+// in one of them.
+func (c *Client) Define(dict, word string) ([]*Defn, error) {
+	id, err := c.text.Cmd("DEFINE %s %q", dict, word)
+	if err != nil {
+		return nil, err
+	}
+
+	c.text.StartResponse(id)
+	defer c.text.EndResponse(id)
+
+	_, line, err := c.text.ReadCodeLine(150)
+	if err != nil {
+		return nil, err
+	}
+	a, _ := fields(line)
+	if len(a) < 1 {
+		return nil, textproto.ProtocolError("malformed response: " + line)
+	}
+	n, err := strconv.Atoi(a[0])
+	if err != nil {
+		return nil, textproto.ProtocolError("invalid definition count: " + a[0])
+	}
+	def := make([]*Defn, n)
+	for i := 0; i < n; i++ {
+		_, line, err = c.text.ReadCodeLine(151)
+		if err != nil {
+			return nil, err
+		}
+		a, _ := fields(line)
+		if len(a) < 3 {
+			// skip it, to keep protocol in sync
+			i--
+			n--
+			def = def[0:n]
+			continue
+		}
+		d := &Defn{Word: a[0], Dict: Dict{a[1], a[2]}}
+		d.Text, err = c.text.ReadDotBytes()
+		if err != nil {
+			return nil, err
+		}
+		def[i] = d
+	}
+	_, _, err = c.text.ReadCodeLine(250)
+	return def, err
+}
+
+// Fields returns the fields in s.
+// Fields are space separated unquoted words
+// or quoted with single or double quote.
+func fields(s string) ([]string, error) {
+	var v []string
+	i := 0
+	for {
+		for i < len(s) && (s[i] == ' ' || s[i] == '\t') {
+			i++
+		}
+		if i >= len(s) {
+			break
+		}
+		if s[i] == '"' || s[i] == '\'' {
+			q := s[i]
+			// quoted string
+			var j int
+			for j = i + 1; ; j++ {
+				if j >= len(s) {
+					return nil, textproto.ProtocolError("malformed quoted string")
+				}
+				if s[j] == '\\' {
+					j++
+					continue
+				}
+				if s[j] == q {
+					j++
+					break
+				}
+			}
+			v = append(v, unquote(s[i+1:j-1]))
+			i = j
+		} else {
+			// atom
+			var j int
+			for j = i; j < len(s); j++ {
+				if s[j] == ' ' || s[j] == '\t' || s[j] == '\\' || s[j] == '"' || s[j] == '\'' {
+					break
+				}
+			}
+			v = append(v, s[i:j])
+			i = j
+		}
+		if i < len(s) {
+			c := s[i]
+			if c != ' ' && c != '\t' {
+				return nil, textproto.ProtocolError("quotes not on word boundaries")
+			}
+		}
+	}
+	return v, nil
+}
+
+func unquote(s string) string {
+	if strings.Index(s, "\\") < 0 {
+		return s
+	}
+	b := []byte(s)
+	w := 0
+	for r := 0; r < len(b); r++ {
+		c := b[r]
+		if c == '\\' {
+			r++
+			c = b[r]
+		}
+		b[w] = c
+		w++
+	}
+	return string(b[0:w])
+}

+ 312 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/spdy/read.go

@@ -0,0 +1,312 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package spdy
+
+import (
+	"compress/zlib"
+	"encoding/binary"
+	"io"
+	"net/http"
+	"strings"
+)
+
+func (frame *SynStreamFrame) read(h ControlFrameHeader, f *Framer) error {
+	return f.readSynStreamFrame(h, frame)
+}
+
+func (frame *SynReplyFrame) read(h ControlFrameHeader, f *Framer) error {
+	return f.readSynReplyFrame(h, frame)
+}
+
+func (frame *RstStreamFrame) read(h ControlFrameHeader, f *Framer) error {
+	frame.CFHeader = h
+	if err := binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil {
+		return err
+	}
+	if err := binary.Read(f.r, binary.BigEndian, &frame.Status); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (frame *SettingsFrame) read(h ControlFrameHeader, f *Framer) error {
+	frame.CFHeader = h
+	var numSettings uint32
+	if err := binary.Read(f.r, binary.BigEndian, &numSettings); err != nil {
+		return err
+	}
+	frame.FlagIdValues = make([]SettingsFlagIdValue, numSettings)
+	for i := uint32(0); i < numSettings; i++ {
+		if err := binary.Read(f.r, binary.BigEndian, &frame.FlagIdValues[i].Id); err != nil {
+			return err
+		}
+		frame.FlagIdValues[i].Flag = SettingsFlag((frame.FlagIdValues[i].Id & 0xff000000) >> 24)
+		frame.FlagIdValues[i].Id &= 0xffffff
+		if err := binary.Read(f.r, binary.BigEndian, &frame.FlagIdValues[i].Value); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (frame *NoopFrame) read(h ControlFrameHeader, f *Framer) error {
+	frame.CFHeader = h
+	return nil
+}
+
+func (frame *PingFrame) read(h ControlFrameHeader, f *Framer) error {
+	frame.CFHeader = h
+	if err := binary.Read(f.r, binary.BigEndian, &frame.Id); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (frame *GoAwayFrame) read(h ControlFrameHeader, f *Framer) error {
+	frame.CFHeader = h
+	if err := binary.Read(f.r, binary.BigEndian, &frame.LastGoodStreamId); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (frame *HeadersFrame) read(h ControlFrameHeader, f *Framer) error {
+	return f.readHeadersFrame(h, frame)
+}
+
+func newControlFrame(frameType ControlFrameType) (controlFrame, error) {
+	ctor, ok := cframeCtor[frameType]
+	if !ok {
+		return nil, &Error{Err: InvalidControlFrame}
+	}
+	return ctor(), nil
+}
+
+var cframeCtor = map[ControlFrameType]func() controlFrame{
+	TypeSynStream: func() controlFrame { return new(SynStreamFrame) },
+	TypeSynReply:  func() controlFrame { return new(SynReplyFrame) },
+	TypeRstStream: func() controlFrame { return new(RstStreamFrame) },
+	TypeSettings:  func() controlFrame { return new(SettingsFrame) },
+	TypeNoop:      func() controlFrame { return new(NoopFrame) },
+	TypePing:      func() controlFrame { return new(PingFrame) },
+	TypeGoAway:    func() controlFrame { return new(GoAwayFrame) },
+	TypeHeaders:   func() controlFrame { return new(HeadersFrame) },
+	// TODO(willchan): Add TypeWindowUpdate
+}
+
+func (f *Framer) uncorkHeaderDecompressor(payloadSize int64) error {
+	if f.headerDecompressor != nil {
+		f.headerReader.N = payloadSize
+		return nil
+	}
+	f.headerReader = io.LimitedReader{R: f.r, N: payloadSize}
+	decompressor, err := zlib.NewReaderDict(&f.headerReader, []byte(HeaderDictionary))
+	if err != nil {
+		return err
+	}
+	f.headerDecompressor = decompressor
+	return nil
+}
+
+// ReadFrame reads SPDY encoded data and returns a decompressed Frame.
+func (f *Framer) ReadFrame() (Frame, error) {
+	var firstWord uint32
+	if err := binary.Read(f.r, binary.BigEndian, &firstWord); err != nil {
+		return nil, err
+	}
+	if (firstWord & 0x80000000) != 0 {
+		frameType := ControlFrameType(firstWord & 0xffff)
+		version := uint16(0x7fff & (firstWord >> 16))
+		return f.parseControlFrame(version, frameType)
+	}
+	return f.parseDataFrame(firstWord & 0x7fffffff)
+}
+
+func (f *Framer) parseControlFrame(version uint16, frameType ControlFrameType) (Frame, error) {
+	var length uint32
+	if err := binary.Read(f.r, binary.BigEndian, &length); err != nil {
+		return nil, err
+	}
+	flags := ControlFlags((length & 0xff000000) >> 24)
+	length &= 0xffffff
+	header := ControlFrameHeader{version, frameType, flags, length}
+	cframe, err := newControlFrame(frameType)
+	if err != nil {
+		return nil, err
+	}
+	if err = cframe.read(header, f); err != nil {
+		return nil, err
+	}
+	return cframe, nil
+}
+
+func parseHeaderValueBlock(r io.Reader, streamId uint32) (http.Header, error) {
+	var numHeaders uint16
+	if err := binary.Read(r, binary.BigEndian, &numHeaders); err != nil {
+		return nil, err
+	}
+	var e error
+	h := make(http.Header, int(numHeaders))
+	for i := 0; i < int(numHeaders); i++ {
+		var length uint16
+		if err := binary.Read(r, binary.BigEndian, &length); err != nil {
+			return nil, err
+		}
+		nameBytes := make([]byte, length)
+		if _, err := io.ReadFull(r, nameBytes); err != nil {
+			return nil, err
+		}
+		name := string(nameBytes)
+		if name != strings.ToLower(name) {
+			e = &Error{UnlowercasedHeaderName, streamId}
+			name = strings.ToLower(name)
+		}
+		if h[name] != nil {
+			e = &Error{DuplicateHeaders, streamId}
+		}
+		if err := binary.Read(r, binary.BigEndian, &length); err != nil {
+			return nil, err
+		}
+		value := make([]byte, length)
+		if _, err := io.ReadFull(r, value); err != nil {
+			return nil, err
+		}
+		valueList := strings.Split(string(value), "\x00")
+		for _, v := range valueList {
+			h.Add(name, v)
+		}
+	}
+	if e != nil {
+		return h, e
+	}
+	return h, nil
+}
+
+func (f *Framer) readSynStreamFrame(h ControlFrameHeader, frame *SynStreamFrame) error {
+	frame.CFHeader = h
+	var err error
+	if err = binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil {
+		return err
+	}
+	if err = binary.Read(f.r, binary.BigEndian, &frame.AssociatedToStreamId); err != nil {
+		return err
+	}
+	if err = binary.Read(f.r, binary.BigEndian, &frame.Priority); err != nil {
+		return err
+	}
+	frame.Priority >>= 14
+
+	reader := f.r
+	if !f.headerCompressionDisabled {
+		f.uncorkHeaderDecompressor(int64(h.length - 10))
+		reader = f.headerDecompressor
+	}
+
+	frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId)
+	if !f.headerCompressionDisabled && ((err == io.EOF && f.headerReader.N == 0) || f.headerReader.N != 0) {
+		err = &Error{WrongCompressedPayloadSize, 0}
+	}
+	if err != nil {
+		return err
+	}
+	// Remove this condition when we bump Version to 3.
+	if Version >= 3 {
+		for h := range frame.Headers {
+			if invalidReqHeaders[h] {
+				return &Error{InvalidHeaderPresent, frame.StreamId}
+			}
+		}
+	}
+	return nil
+}
+
+func (f *Framer) readSynReplyFrame(h ControlFrameHeader, frame *SynReplyFrame) error {
+	frame.CFHeader = h
+	var err error
+	if err = binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil {
+		return err
+	}
+	var unused uint16
+	if err = binary.Read(f.r, binary.BigEndian, &unused); err != nil {
+		return err
+	}
+	reader := f.r
+	if !f.headerCompressionDisabled {
+		f.uncorkHeaderDecompressor(int64(h.length - 6))
+		reader = f.headerDecompressor
+	}
+	frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId)
+	if !f.headerCompressionDisabled && ((err == io.EOF && f.headerReader.N == 0) || f.headerReader.N != 0) {
+		err = &Error{WrongCompressedPayloadSize, 0}
+	}
+	if err != nil {
+		return err
+	}
+	// Remove this condition when we bump Version to 3.
+	if Version >= 3 {
+		for h := range frame.Headers {
+			if invalidRespHeaders[h] {
+				return &Error{InvalidHeaderPresent, frame.StreamId}
+			}
+		}
+	}
+	return nil
+}
+
+func (f *Framer) readHeadersFrame(h ControlFrameHeader, frame *HeadersFrame) error {
+	frame.CFHeader = h
+	var err error
+	if err = binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil {
+		return err
+	}
+	var unused uint16
+	if err = binary.Read(f.r, binary.BigEndian, &unused); err != nil {
+		return err
+	}
+	reader := f.r
+	if !f.headerCompressionDisabled {
+		f.uncorkHeaderDecompressor(int64(h.length - 6))
+		reader = f.headerDecompressor
+	}
+	frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId)
+	if !f.headerCompressionDisabled && ((err == io.EOF && f.headerReader.N == 0) || f.headerReader.N != 0) {
+		err = &Error{WrongCompressedPayloadSize, 0}
+	}
+	if err != nil {
+		return err
+	}
+
+	// Remove this condition when we bump Version to 3.
+	if Version >= 3 {
+		var invalidHeaders map[string]bool
+		if frame.StreamId%2 == 0 {
+			invalidHeaders = invalidReqHeaders
+		} else {
+			invalidHeaders = invalidRespHeaders
+		}
+		for h := range frame.Headers {
+			if invalidHeaders[h] {
+				return &Error{InvalidHeaderPresent, frame.StreamId}
+			}
+		}
+	}
+	return nil
+}
+
+func (f *Framer) parseDataFrame(streamId uint32) (*DataFrame, error) {
+	var length uint32
+	if err := binary.Read(f.r, binary.BigEndian, &length); err != nil {
+		return nil, err
+	}
+	var frame DataFrame
+	frame.StreamId = streamId
+	frame.Flags = DataFlags(length >> 24)
+	length &= 0xffffff
+	frame.Data = make([]byte, length)
+	if _, err := io.ReadFull(f.r, frame.Data); err != nil {
+		return nil, err
+	}
+	return &frame, nil
+}

+ 497 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/spdy/spdy_test.go

@@ -0,0 +1,497 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package spdy
+
+import (
+	"bytes"
+	"io"
+	"net/http"
+	"reflect"
+	"testing"
+)
+
+func TestHeaderParsing(t *testing.T) {
+	headers := http.Header{
+		"Url":     []string{"http://www.google.com/"},
+		"Method":  []string{"get"},
+		"Version": []string{"http/1.1"},
+	}
+	var headerValueBlockBuf bytes.Buffer
+	writeHeaderValueBlock(&headerValueBlockBuf, headers)
+
+	const bogusStreamId = 1
+	newHeaders, err := parseHeaderValueBlock(&headerValueBlockBuf, bogusStreamId)
+	if err != nil {
+		t.Fatal("parseHeaderValueBlock:", err)
+	}
+
+	if !reflect.DeepEqual(headers, newHeaders) {
+		t.Fatal("got: ", newHeaders, "\nwant: ", headers)
+	}
+}
+
+func TestCreateParseSynStreamFrame(t *testing.T) {
+	buffer := new(bytes.Buffer)
+	framer := &Framer{
+		headerCompressionDisabled: true,
+		w:                         buffer,
+		headerBuf:                 new(bytes.Buffer),
+		r:                         buffer,
+	}
+	synStreamFrame := SynStreamFrame{
+		CFHeader: ControlFrameHeader{
+			version:   Version,
+			frameType: TypeSynStream,
+		},
+		Headers: http.Header{
+			"Url":     []string{"http://www.google.com/"},
+			"Method":  []string{"get"},
+			"Version": []string{"http/1.1"},
+		},
+	}
+	if err := framer.WriteFrame(&synStreamFrame); err != nil {
+		t.Fatal("WriteFrame without compression:", err)
+	}
+	frame, err := framer.ReadFrame()
+	if err != nil {
+		t.Fatal("ReadFrame without compression:", err)
+	}
+	parsedSynStreamFrame, ok := frame.(*SynStreamFrame)
+	if !ok {
+		t.Fatal("Parsed incorrect frame type:", frame)
+	}
+	if !reflect.DeepEqual(synStreamFrame, *parsedSynStreamFrame) {
+		t.Fatal("got: ", *parsedSynStreamFrame, "\nwant: ", synStreamFrame)
+	}
+
+	// Test again with compression
+	buffer.Reset()
+	framer, err = NewFramer(buffer, buffer)
+	if err != nil {
+		t.Fatal("Failed to create new framer:", err)
+	}
+	if err := framer.WriteFrame(&synStreamFrame); err != nil {
+		t.Fatal("WriteFrame with compression:", err)
+	}
+	frame, err = framer.ReadFrame()
+	if err != nil {
+		t.Fatal("ReadFrame with compression:", err)
+	}
+	parsedSynStreamFrame, ok = frame.(*SynStreamFrame)
+	if !ok {
+		t.Fatal("Parsed incorrect frame type:", frame)
+	}
+	if !reflect.DeepEqual(synStreamFrame, *parsedSynStreamFrame) {
+		t.Fatal("got: ", *parsedSynStreamFrame, "\nwant: ", synStreamFrame)
+	}
+}
+
+func TestCreateParseSynReplyFrame(t *testing.T) {
+	buffer := new(bytes.Buffer)
+	framer := &Framer{
+		headerCompressionDisabled: true,
+		w:                         buffer,
+		headerBuf:                 new(bytes.Buffer),
+		r:                         buffer,
+	}
+	synReplyFrame := SynReplyFrame{
+		CFHeader: ControlFrameHeader{
+			version:   Version,
+			frameType: TypeSynReply,
+		},
+		Headers: http.Header{
+			"Url":     []string{"http://www.google.com/"},
+			"Method":  []string{"get"},
+			"Version": []string{"http/1.1"},
+		},
+	}
+	if err := framer.WriteFrame(&synReplyFrame); err != nil {
+		t.Fatal("WriteFrame without compression:", err)
+	}
+	frame, err := framer.ReadFrame()
+	if err != nil {
+		t.Fatal("ReadFrame without compression:", err)
+	}
+	parsedSynReplyFrame, ok := frame.(*SynReplyFrame)
+	if !ok {
+		t.Fatal("Parsed incorrect frame type:", frame)
+	}
+	if !reflect.DeepEqual(synReplyFrame, *parsedSynReplyFrame) {
+		t.Fatal("got: ", *parsedSynReplyFrame, "\nwant: ", synReplyFrame)
+	}
+
+	// Test again with compression
+	buffer.Reset()
+	framer, err = NewFramer(buffer, buffer)
+	if err != nil {
+		t.Fatal("Failed to create new framer:", err)
+	}
+	if err := framer.WriteFrame(&synReplyFrame); err != nil {
+		t.Fatal("WriteFrame with compression:", err)
+	}
+	frame, err = framer.ReadFrame()
+	if err != nil {
+		t.Fatal("ReadFrame with compression:", err)
+	}
+	parsedSynReplyFrame, ok = frame.(*SynReplyFrame)
+	if !ok {
+		t.Fatal("Parsed incorrect frame type:", frame)
+	}
+	if !reflect.DeepEqual(synReplyFrame, *parsedSynReplyFrame) {
+		t.Fatal("got: ", *parsedSynReplyFrame, "\nwant: ", synReplyFrame)
+	}
+}
+
+func TestCreateParseRstStream(t *testing.T) {
+	buffer := new(bytes.Buffer)
+	framer, err := NewFramer(buffer, buffer)
+	if err != nil {
+		t.Fatal("Failed to create new framer:", err)
+	}
+	rstStreamFrame := RstStreamFrame{
+		CFHeader: ControlFrameHeader{
+			version:   Version,
+			frameType: TypeRstStream,
+		},
+		StreamId: 1,
+		Status:   InvalidStream,
+	}
+	if err := framer.WriteFrame(&rstStreamFrame); err != nil {
+		t.Fatal("WriteFrame:", err)
+	}
+	frame, err := framer.ReadFrame()
+	if err != nil {
+		t.Fatal("ReadFrame:", err)
+	}
+	parsedRstStreamFrame, ok := frame.(*RstStreamFrame)
+	if !ok {
+		t.Fatal("Parsed incorrect frame type:", frame)
+	}
+	if !reflect.DeepEqual(rstStreamFrame, *parsedRstStreamFrame) {
+		t.Fatal("got: ", *parsedRstStreamFrame, "\nwant: ", rstStreamFrame)
+	}
+}
+
+func TestCreateParseSettings(t *testing.T) {
+	buffer := new(bytes.Buffer)
+	framer, err := NewFramer(buffer, buffer)
+	if err != nil {
+		t.Fatal("Failed to create new framer:", err)
+	}
+	settingsFrame := SettingsFrame{
+		CFHeader: ControlFrameHeader{
+			version:   Version,
+			frameType: TypeSettings,
+		},
+		FlagIdValues: []SettingsFlagIdValue{
+			{FlagSettingsPersistValue, SettingsCurrentCwnd, 10},
+			{FlagSettingsPersisted, SettingsUploadBandwidth, 1},
+		},
+	}
+	if err := framer.WriteFrame(&settingsFrame); err != nil {
+		t.Fatal("WriteFrame:", err)
+	}
+	frame, err := framer.ReadFrame()
+	if err != nil {
+		t.Fatal("ReadFrame:", err)
+	}
+	parsedSettingsFrame, ok := frame.(*SettingsFrame)
+	if !ok {
+		t.Fatal("Parsed incorrect frame type:", frame)
+	}
+	if !reflect.DeepEqual(settingsFrame, *parsedSettingsFrame) {
+		t.Fatal("got: ", *parsedSettingsFrame, "\nwant: ", settingsFrame)
+	}
+}
+
+func TestCreateParseNoop(t *testing.T) {
+	buffer := new(bytes.Buffer)
+	framer, err := NewFramer(buffer, buffer)
+	if err != nil {
+		t.Fatal("Failed to create new framer:", err)
+	}
+	noopFrame := NoopFrame{
+		CFHeader: ControlFrameHeader{
+			version:   Version,
+			frameType: TypeNoop,
+		},
+	}
+	if err := framer.WriteFrame(&noopFrame); err != nil {
+		t.Fatal("WriteFrame:", err)
+	}
+	frame, err := framer.ReadFrame()
+	if err != nil {
+		t.Fatal("ReadFrame:", err)
+	}
+	parsedNoopFrame, ok := frame.(*NoopFrame)
+	if !ok {
+		t.Fatal("Parsed incorrect frame type:", frame)
+	}
+	if !reflect.DeepEqual(noopFrame, *parsedNoopFrame) {
+		t.Fatal("got: ", *parsedNoopFrame, "\nwant: ", noopFrame)
+	}
+}
+
+func TestCreateParsePing(t *testing.T) {
+	buffer := new(bytes.Buffer)
+	framer, err := NewFramer(buffer, buffer)
+	if err != nil {
+		t.Fatal("Failed to create new framer:", err)
+	}
+	pingFrame := PingFrame{
+		CFHeader: ControlFrameHeader{
+			version:   Version,
+			frameType: TypePing,
+		},
+		Id: 31337,
+	}
+	if err := framer.WriteFrame(&pingFrame); err != nil {
+		t.Fatal("WriteFrame:", err)
+	}
+	frame, err := framer.ReadFrame()
+	if err != nil {
+		t.Fatal("ReadFrame:", err)
+	}
+	parsedPingFrame, ok := frame.(*PingFrame)
+	if !ok {
+		t.Fatal("Parsed incorrect frame type:", frame)
+	}
+	if !reflect.DeepEqual(pingFrame, *parsedPingFrame) {
+		t.Fatal("got: ", *parsedPingFrame, "\nwant: ", pingFrame)
+	}
+}
+
+func TestCreateParseGoAway(t *testing.T) {
+	buffer := new(bytes.Buffer)
+	framer, err := NewFramer(buffer, buffer)
+	if err != nil {
+		t.Fatal("Failed to create new framer:", err)
+	}
+	goAwayFrame := GoAwayFrame{
+		CFHeader: ControlFrameHeader{
+			version:   Version,
+			frameType: TypeGoAway,
+		},
+		LastGoodStreamId: 31337,
+	}
+	if err := framer.WriteFrame(&goAwayFrame); err != nil {
+		t.Fatal("WriteFrame:", err)
+	}
+	frame, err := framer.ReadFrame()
+	if err != nil {
+		t.Fatal("ReadFrame:", err)
+	}
+	parsedGoAwayFrame, ok := frame.(*GoAwayFrame)
+	if !ok {
+		t.Fatal("Parsed incorrect frame type:", frame)
+	}
+	if !reflect.DeepEqual(goAwayFrame, *parsedGoAwayFrame) {
+		t.Fatal("got: ", *parsedGoAwayFrame, "\nwant: ", goAwayFrame)
+	}
+}
+
+func TestCreateParseHeadersFrame(t *testing.T) {
+	buffer := new(bytes.Buffer)
+	framer := &Framer{
+		headerCompressionDisabled: true,
+		w:                         buffer,
+		headerBuf:                 new(bytes.Buffer),
+		r:                         buffer,
+	}
+	headersFrame := HeadersFrame{
+		CFHeader: ControlFrameHeader{
+			version:   Version,
+			frameType: TypeHeaders,
+		},
+	}
+	headersFrame.Headers = http.Header{
+		"Url":     []string{"http://www.google.com/"},
+		"Method":  []string{"get"},
+		"Version": []string{"http/1.1"},
+	}
+	if err := framer.WriteFrame(&headersFrame); err != nil {
+		t.Fatal("WriteFrame without compression:", err)
+	}
+	frame, err := framer.ReadFrame()
+	if err != nil {
+		t.Fatal("ReadFrame without compression:", err)
+	}
+	parsedHeadersFrame, ok := frame.(*HeadersFrame)
+	if !ok {
+		t.Fatal("Parsed incorrect frame type:", frame)
+	}
+	if !reflect.DeepEqual(headersFrame, *parsedHeadersFrame) {
+		t.Fatal("got: ", *parsedHeadersFrame, "\nwant: ", headersFrame)
+	}
+
+	// Test again with compression
+	buffer.Reset()
+	framer, err = NewFramer(buffer, buffer)
+	if err := framer.WriteFrame(&headersFrame); err != nil {
+		t.Fatal("WriteFrame with compression:", err)
+	}
+	frame, err = framer.ReadFrame()
+	if err != nil {
+		t.Fatal("ReadFrame with compression:", err)
+	}
+	parsedHeadersFrame, ok = frame.(*HeadersFrame)
+	if !ok {
+		t.Fatal("Parsed incorrect frame type:", frame)
+	}
+	if !reflect.DeepEqual(headersFrame, *parsedHeadersFrame) {
+		t.Fatal("got: ", *parsedHeadersFrame, "\nwant: ", headersFrame)
+	}
+}
+
+func TestCreateParseDataFrame(t *testing.T) {
+	buffer := new(bytes.Buffer)
+	framer, err := NewFramer(buffer, buffer)
+	if err != nil {
+		t.Fatal("Failed to create new framer:", err)
+	}
+	dataFrame := DataFrame{
+		StreamId: 1,
+		Data:     []byte{'h', 'e', 'l', 'l', 'o'},
+	}
+	if err := framer.WriteFrame(&dataFrame); err != nil {
+		t.Fatal("WriteFrame:", err)
+	}
+	frame, err := framer.ReadFrame()
+	if err != nil {
+		t.Fatal("ReadFrame:", err)
+	}
+	parsedDataFrame, ok := frame.(*DataFrame)
+	if !ok {
+		t.Fatal("Parsed incorrect frame type:", frame)
+	}
+	if !reflect.DeepEqual(dataFrame, *parsedDataFrame) {
+		t.Fatal("got: ", *parsedDataFrame, "\nwant: ", dataFrame)
+	}
+}
+
+func TestCompressionContextAcrossFrames(t *testing.T) {
+	buffer := new(bytes.Buffer)
+	framer, err := NewFramer(buffer, buffer)
+	if err != nil {
+		t.Fatal("Failed to create new framer:", err)
+	}
+	headersFrame := HeadersFrame{
+		CFHeader: ControlFrameHeader{
+			version:   Version,
+			frameType: TypeHeaders,
+		},
+		Headers: http.Header{
+			"Url":     []string{"http://www.google.com/"},
+			"Method":  []string{"get"},
+			"Version": []string{"http/1.1"},
+		},
+	}
+	if err := framer.WriteFrame(&headersFrame); err != nil {
+		t.Fatal("WriteFrame (HEADERS):", err)
+	}
+	synStreamFrame := SynStreamFrame{ControlFrameHeader{Version, TypeSynStream, 0, 0}, 0, 0, 0, nil}
+	synStreamFrame.Headers = http.Header{
+		"Url":     []string{"http://www.google.com/"},
+		"Method":  []string{"get"},
+		"Version": []string{"http/1.1"},
+	}
+	if err := framer.WriteFrame(&synStreamFrame); err != nil {
+		t.Fatal("WriteFrame (SYN_STREAM):", err)
+	}
+	frame, err := framer.ReadFrame()
+	if err != nil {
+		t.Fatal("ReadFrame (HEADERS):", err, buffer.Bytes())
+	}
+	parsedHeadersFrame, ok := frame.(*HeadersFrame)
+	if !ok {
+		t.Fatalf("expected HeadersFrame; got %T %v", frame, frame)
+	}
+	if !reflect.DeepEqual(headersFrame, *parsedHeadersFrame) {
+		t.Fatal("got: ", *parsedHeadersFrame, "\nwant: ", headersFrame)
+	}
+	frame, err = framer.ReadFrame()
+	if err != nil {
+		t.Fatal("ReadFrame (SYN_STREAM):", err, buffer.Bytes())
+	}
+	parsedSynStreamFrame, ok := frame.(*SynStreamFrame)
+	if !ok {
+		t.Fatalf("expected SynStreamFrame; got %T %v", frame, frame)
+	}
+	if !reflect.DeepEqual(synStreamFrame, *parsedSynStreamFrame) {
+		t.Fatal("got: ", *parsedSynStreamFrame, "\nwant: ", synStreamFrame)
+	}
+}
+
+func TestMultipleSPDYFrames(t *testing.T) {
+	// Initialize the framers.
+	pr1, pw1 := io.Pipe()
+	pr2, pw2 := io.Pipe()
+	writer, err := NewFramer(pw1, pr2)
+	if err != nil {
+		t.Fatal("Failed to create writer:", err)
+	}
+	reader, err := NewFramer(pw2, pr1)
+	if err != nil {
+		t.Fatal("Failed to create reader:", err)
+	}
+
+	// Set up the frames we're actually transferring.
+	headersFrame := HeadersFrame{
+		CFHeader: ControlFrameHeader{
+			version:   Version,
+			frameType: TypeHeaders,
+		},
+		Headers: http.Header{
+			"Url":     []string{"http://www.google.com/"},
+			"Method":  []string{"get"},
+			"Version": []string{"http/1.1"},
+		},
+	}
+	synStreamFrame := SynStreamFrame{
+		CFHeader: ControlFrameHeader{
+			version:   Version,
+			frameType: TypeSynStream,
+		},
+		Headers: http.Header{
+			"Url":     []string{"http://www.google.com/"},
+			"Method":  []string{"get"},
+			"Version": []string{"http/1.1"},
+		},
+	}
+
+	// Start the goroutines to write the frames.
+	go func() {
+		if err := writer.WriteFrame(&headersFrame); err != nil {
+			t.Fatal("WriteFrame (HEADERS): ", err)
+		}
+		if err := writer.WriteFrame(&synStreamFrame); err != nil {
+			t.Fatal("WriteFrame (SYN_STREAM): ", err)
+		}
+	}()
+
+	// Read the frames and verify they look as expected.
+	frame, err := reader.ReadFrame()
+	if err != nil {
+		t.Fatal("ReadFrame (HEADERS): ", err)
+	}
+	parsedHeadersFrame, ok := frame.(*HeadersFrame)
+	if !ok {
+		t.Fatal("Parsed incorrect frame type:", frame)
+	}
+	if !reflect.DeepEqual(headersFrame, *parsedHeadersFrame) {
+		t.Fatal("got: ", *parsedHeadersFrame, "\nwant: ", headersFrame)
+	}
+	frame, err = reader.ReadFrame()
+	if err != nil {
+		t.Fatal("ReadFrame (SYN_STREAM):", err)
+	}
+	parsedSynStreamFrame, ok := frame.(*SynStreamFrame)
+	if !ok {
+		t.Fatal("Parsed incorrect frame type.")
+	}
+	if !reflect.DeepEqual(synStreamFrame, *parsedSynStreamFrame) {
+		t.Fatal("got: ", *parsedSynStreamFrame, "\nwant: ", synStreamFrame)
+	}
+}

+ 369 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/spdy/types.go

@@ -0,0 +1,369 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package spdy
+
+import (
+	"bytes"
+	"compress/zlib"
+	"io"
+	"net/http"
+)
+
+//  Data Frame Format
+//  +----------------------------------+
+//  |0|       Stream-ID (31bits)       |
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   |
+//  +----------------------------------+
+//  |               Data               |
+//  +----------------------------------+
+//
+//  Control Frame Format
+//  +----------------------------------+
+//  |1| Version(15bits) | Type(16bits) |
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   |
+//  +----------------------------------+
+//  |               Data               |
+//  +----------------------------------+
+//
+//  Control Frame: SYN_STREAM
+//  +----------------------------------+
+//  |1|000000000000001|0000000000000001|
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   |  >= 12
+//  +----------------------------------+
+//  |X|       Stream-ID(31bits)        |
+//  +----------------------------------+
+//  |X|Associated-To-Stream-ID (31bits)|
+//  +----------------------------------+
+//  |Pri| unused      | Length (16bits)|
+//  +----------------------------------+
+//
+//  Control Frame: SYN_REPLY
+//  +----------------------------------+
+//  |1|000000000000001|0000000000000010|
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   |  >= 8
+//  +----------------------------------+
+//  |X|       Stream-ID(31bits)        |
+//  +----------------------------------+
+//  | unused (16 bits)| Length (16bits)|
+//  +----------------------------------+
+//
+//  Control Frame: RST_STREAM
+//  +----------------------------------+
+//  |1|000000000000001|0000000000000011|
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   |  >= 4
+//  +----------------------------------+
+//  |X|       Stream-ID(31bits)        |
+//  +----------------------------------+
+//  |        Status code (32 bits)     |
+//  +----------------------------------+
+//
+//  Control Frame: SETTINGS
+//  +----------------------------------+
+//  |1|000000000000001|0000000000000100|
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   |
+//  +----------------------------------+
+//  |        # of entries (32)         |
+//  +----------------------------------+
+//
+//  Control Frame: NOOP
+//  +----------------------------------+
+//  |1|000000000000001|0000000000000101|
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   | = 0
+//  +----------------------------------+
+//
+//  Control Frame: PING
+//  +----------------------------------+
+//  |1|000000000000001|0000000000000110|
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   | = 4
+//  +----------------------------------+
+//  |        Unique id (32 bits)       |
+//  +----------------------------------+
+//
+//  Control Frame: GOAWAY
+//  +----------------------------------+
+//  |1|000000000000001|0000000000000111|
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   | = 4
+//  +----------------------------------+
+//  |X|  Last-accepted-stream-id       |
+//  +----------------------------------+
+//
+//  Control Frame: HEADERS
+//  +----------------------------------+
+//  |1|000000000000001|0000000000001000|
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   | >= 8
+//  +----------------------------------+
+//  |X|      Stream-ID (31 bits)       |
+//  +----------------------------------+
+//  | unused (16 bits)| Length (16bits)|
+//  +----------------------------------+
+//
+//  Control Frame: WINDOW_UPDATE
+//  +----------------------------------+
+//  |1|000000000000001|0000000000001001|
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   | = 8
+//  +----------------------------------+
+//  |X|      Stream-ID (31 bits)       |
+//  +----------------------------------+
+//  |   Delta-Window-Size (32 bits)    |
+//  +----------------------------------+
+
+// Version is the protocol version number that this package implements.
+const Version = 2
+
+// ControlFrameType stores the type field in a control frame header.
+type ControlFrameType uint16
+
+// Control frame type constants
+const (
+	TypeSynStream    ControlFrameType = 0x0001
+	TypeSynReply                      = 0x0002
+	TypeRstStream                     = 0x0003
+	TypeSettings                      = 0x0004
+	TypeNoop                          = 0x0005
+	TypePing                          = 0x0006
+	TypeGoAway                        = 0x0007
+	TypeHeaders                       = 0x0008
+	TypeWindowUpdate                  = 0x0009
+)
+
+// ControlFlags are the flags that can be set on a control frame.
+type ControlFlags uint8
+
+const (
+	ControlFlagFin ControlFlags = 0x01
+)
+
+// DataFlags are the flags that can be set on a data frame.
+type DataFlags uint8
+
+const (
+	DataFlagFin        DataFlags = 0x01
+	DataFlagCompressed           = 0x02
+)
+
+// MaxDataLength is the maximum number of bytes that can be stored in one frame.
+const MaxDataLength = 1<<24 - 1
+
+// Frame is a single SPDY frame in its unpacked in-memory representation. Use
+// Framer to read and write it.
+type Frame interface {
+	write(f *Framer) error
+}
+
+// ControlFrameHeader contains all the fields in a control frame header,
+// in its unpacked in-memory representation.
+type ControlFrameHeader struct {
+	// Note, high bit is the "Control" bit.
+	version   uint16
+	frameType ControlFrameType
+	Flags     ControlFlags
+	length    uint32
+}
+
+type controlFrame interface {
+	Frame
+	read(h ControlFrameHeader, f *Framer) error
+}
+
+// SynStreamFrame is the unpacked, in-memory representation of a SYN_STREAM
+// frame.
+type SynStreamFrame struct {
+	CFHeader             ControlFrameHeader
+	StreamId             uint32
+	AssociatedToStreamId uint32
+	// Note, only 2 highest bits currently used
+	// Rest of Priority is unused.
+	Priority uint16
+	Headers  http.Header
+}
+
+// SynReplyFrame is the unpacked, in-memory representation of a SYN_REPLY frame.
+type SynReplyFrame struct {
+	CFHeader ControlFrameHeader
+	StreamId uint32
+	Headers  http.Header
+}
+
+// StatusCode represents the status that led to a RST_STREAM
+type StatusCode uint32
+
+const (
+	ProtocolError      StatusCode = 1
+	InvalidStream                 = 2
+	RefusedStream                 = 3
+	UnsupportedVersion            = 4
+	Cancel                        = 5
+	InternalError                 = 6
+	FlowControlError              = 7
+)
+
+// RstStreamFrame is the unpacked, in-memory representation of a RST_STREAM
+// frame.
+type RstStreamFrame struct {
+	CFHeader ControlFrameHeader
+	StreamId uint32
+	Status   StatusCode
+}
+
+// SettingsFlag represents a flag in a SETTINGS frame.
+type SettingsFlag uint8
+
+const (
+	FlagSettingsPersistValue SettingsFlag = 0x1
+	FlagSettingsPersisted                 = 0x2
+)
+
+// SettingsFlag represents the id of an id/value pair in a SETTINGS frame.
+type SettingsId uint32
+
+const (
+	SettingsUploadBandwidth      SettingsId = 1
+	SettingsDownloadBandwidth               = 2
+	SettingsRoundTripTime                   = 3
+	SettingsMaxConcurrentStreams            = 4
+	SettingsCurrentCwnd                     = 5
+)
+
+// SettingsFlagIdValue is the unpacked, in-memory representation of the
+// combined flag/id/value for a setting in a SETTINGS frame.
+type SettingsFlagIdValue struct {
+	Flag  SettingsFlag
+	Id    SettingsId
+	Value uint32
+}
+
+// SettingsFrame is the unpacked, in-memory representation of a SPDY
+// SETTINGS frame.
+type SettingsFrame struct {
+	CFHeader     ControlFrameHeader
+	FlagIdValues []SettingsFlagIdValue
+}
+
+// NoopFrame is the unpacked, in-memory representation of a NOOP frame.
+type NoopFrame struct {
+	CFHeader ControlFrameHeader
+}
+
+// PingFrame is the unpacked, in-memory representation of a PING frame.
+type PingFrame struct {
+	CFHeader ControlFrameHeader
+	Id       uint32
+}
+
+// GoAwayFrame is the unpacked, in-memory representation of a GOAWAY frame.
+type GoAwayFrame struct {
+	CFHeader         ControlFrameHeader
+	LastGoodStreamId uint32
+}
+
+// HeadersFrame is the unpacked, in-memory representation of a HEADERS frame.
+type HeadersFrame struct {
+	CFHeader ControlFrameHeader
+	StreamId uint32
+	Headers  http.Header
+}
+
+// DataFrame is the unpacked, in-memory representation of a DATA frame.
+type DataFrame struct {
+	// Note, high bit is the "Control" bit. Should be 0 for data frames.
+	StreamId uint32
+	Flags    DataFlags
+	Data     []byte
+}
+
+// HeaderDictionary is the dictionary sent to the zlib compressor/decompressor.
+// Even though the specification states there is no null byte at the end, Chrome sends it.
+const HeaderDictionary = "optionsgetheadpostputdeletetrace" +
+	"acceptaccept-charsetaccept-encodingaccept-languageauthorizationexpectfromhost" +
+	"if-modified-sinceif-matchif-none-matchif-rangeif-unmodifiedsince" +
+	"max-forwardsproxy-authorizationrangerefererteuser-agent" +
+	"100101200201202203204205206300301302303304305306307400401402403404405406407408409410411412413414415416417500501502503504505" +
+	"accept-rangesageetaglocationproxy-authenticatepublicretry-after" +
+	"servervarywarningwww-authenticateallowcontent-basecontent-encodingcache-control" +
+	"connectiondatetrailertransfer-encodingupgradeviawarning" +
+	"content-languagecontent-lengthcontent-locationcontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookie" +
+	"MondayTuesdayWednesdayThursdayFridaySaturdaySunday" +
+	"JanFebMarAprMayJunJulAugSepOctNovDec" +
+	"chunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplication/xhtmltext/plainpublicmax-age" +
+	"charset=iso-8859-1utf-8gzipdeflateHTTP/1.1statusversionurl\x00"
+
+// A SPDY specific error.
+type ErrorCode string
+
+const (
+	UnlowercasedHeaderName     ErrorCode = "header was not lowercased"
+	DuplicateHeaders           ErrorCode = "multiple headers with same name"
+	WrongCompressedPayloadSize ErrorCode = "compressed payload size was incorrect"
+	UnknownFrameType           ErrorCode = "unknown frame type"
+	InvalidControlFrame        ErrorCode = "invalid control frame"
+	InvalidDataFrame           ErrorCode = "invalid data frame"
+	InvalidHeaderPresent       ErrorCode = "frame contained invalid header"
+)
+
+// Error contains both the type of error and additional values. StreamId is 0
+// if Error is not associated with a stream.
+type Error struct {
+	Err      ErrorCode
+	StreamId uint32
+}
+
+func (e *Error) Error() string {
+	return string(e.Err)
+}
+
+var invalidReqHeaders = map[string]bool{
+	"Connection":        true,
+	"Keep-Alive":        true,
+	"Proxy-Connection":  true,
+	"Transfer-Encoding": true,
+}
+
+var invalidRespHeaders = map[string]bool{
+	"Connection":        true,
+	"Keep-Alive":        true,
+	"Transfer-Encoding": true,
+}
+
+// Framer handles serializing/deserializing SPDY frames, including compressing/
+// decompressing payloads.
+type Framer struct {
+	headerCompressionDisabled bool
+	w                         io.Writer
+	headerBuf                 *bytes.Buffer
+	headerCompressor          *zlib.Writer
+	r                         io.Reader
+	headerReader              io.LimitedReader
+	headerDecompressor        io.ReadCloser
+}
+
+// NewFramer allocates a new Framer for a given SPDY connection, repesented by
+// a io.Writer and io.Reader. Note that Framer will read and write individual fields 
+// from/to the Reader and Writer, so the caller should pass in an appropriately 
+// buffered implementation to optimize performance.
+func NewFramer(w io.Writer, r io.Reader) (*Framer, error) {
+	compressBuf := new(bytes.Buffer)
+	compressor, err := zlib.NewWriterLevelDict(compressBuf, zlib.BestCompression, []byte(HeaderDictionary))
+	if err != nil {
+		return nil, err
+	}
+	framer := &Framer{
+		w:                w,
+		headerBuf:        compressBuf,
+		headerCompressor: compressor,
+		r:                r,
+	}
+	return framer, nil
+}

+ 285 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/spdy/write.go

@@ -0,0 +1,285 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package spdy
+
+import (
+	"encoding/binary"
+	"io"
+	"net/http"
+	"strings"
+)
+
+func (frame *SynStreamFrame) write(f *Framer) error {
+	return f.writeSynStreamFrame(frame)
+}
+
+func (frame *SynReplyFrame) write(f *Framer) error {
+	return f.writeSynReplyFrame(frame)
+}
+
+func (frame *RstStreamFrame) write(f *Framer) (err error) {
+	frame.CFHeader.version = Version
+	frame.CFHeader.frameType = TypeRstStream
+	frame.CFHeader.length = 8
+
+	// Serialize frame to Writer
+	if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
+		return
+	}
+	if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
+		return
+	}
+	if err = binary.Write(f.w, binary.BigEndian, frame.Status); err != nil {
+		return
+	}
+	return
+}
+
+func (frame *SettingsFrame) write(f *Framer) (err error) {
+	frame.CFHeader.version = Version
+	frame.CFHeader.frameType = TypeSettings
+	frame.CFHeader.length = uint32(len(frame.FlagIdValues)*8 + 4)
+
+	// Serialize frame to Writer
+	if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
+		return
+	}
+	if err = binary.Write(f.w, binary.BigEndian, uint32(len(frame.FlagIdValues))); err != nil {
+		return
+	}
+	for _, flagIdValue := range frame.FlagIdValues {
+		flagId := (uint32(flagIdValue.Flag) << 24) | uint32(flagIdValue.Id)
+		if err = binary.Write(f.w, binary.BigEndian, flagId); err != nil {
+			return
+		}
+		if err = binary.Write(f.w, binary.BigEndian, flagIdValue.Value); err != nil {
+			return
+		}
+	}
+	return
+}
+
+func (frame *NoopFrame) write(f *Framer) error {
+	frame.CFHeader.version = Version
+	frame.CFHeader.frameType = TypeNoop
+
+	// Serialize frame to Writer
+	return writeControlFrameHeader(f.w, frame.CFHeader)
+}
+
+func (frame *PingFrame) write(f *Framer) (err error) {
+	frame.CFHeader.version = Version
+	frame.CFHeader.frameType = TypePing
+	frame.CFHeader.length = 4
+
+	// Serialize frame to Writer
+	if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
+		return
+	}
+	if err = binary.Write(f.w, binary.BigEndian, frame.Id); err != nil {
+		return
+	}
+	return
+}
+
+func (frame *GoAwayFrame) write(f *Framer) (err error) {
+	frame.CFHeader.version = Version
+	frame.CFHeader.frameType = TypeGoAway
+	frame.CFHeader.length = 4
+
+	// Serialize frame to Writer
+	if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
+		return
+	}
+	if err = binary.Write(f.w, binary.BigEndian, frame.LastGoodStreamId); err != nil {
+		return
+	}
+	return nil
+}
+
+func (frame *HeadersFrame) write(f *Framer) error {
+	return f.writeHeadersFrame(frame)
+}
+
+func (frame *DataFrame) write(f *Framer) error {
+	return f.writeDataFrame(frame)
+}
+
+// WriteFrame writes a frame.
+func (f *Framer) WriteFrame(frame Frame) error {
+	return frame.write(f)
+}
+
+func writeControlFrameHeader(w io.Writer, h ControlFrameHeader) error {
+	if err := binary.Write(w, binary.BigEndian, 0x8000|h.version); err != nil {
+		return err
+	}
+	if err := binary.Write(w, binary.BigEndian, h.frameType); err != nil {
+		return err
+	}
+	flagsAndLength := (uint32(h.Flags) << 24) | h.length
+	if err := binary.Write(w, binary.BigEndian, flagsAndLength); err != nil {
+		return err
+	}
+	return nil
+}
+
+func writeHeaderValueBlock(w io.Writer, h http.Header) (n int, err error) {
+	n = 0
+	if err = binary.Write(w, binary.BigEndian, uint16(len(h))); err != nil {
+		return
+	}
+	n += 2
+	for name, values := range h {
+		if err = binary.Write(w, binary.BigEndian, uint16(len(name))); err != nil {
+			return
+		}
+		n += 2
+		name = strings.ToLower(name)
+		if _, err = io.WriteString(w, name); err != nil {
+			return
+		}
+		n += len(name)
+		v := strings.Join(values, "\x00")
+		if err = binary.Write(w, binary.BigEndian, uint16(len(v))); err != nil {
+			return
+		}
+		n += 2
+		if _, err = io.WriteString(w, v); err != nil {
+			return
+		}
+		n += len(v)
+	}
+	return
+}
+
+func (f *Framer) writeSynStreamFrame(frame *SynStreamFrame) (err error) {
+	// Marshal the headers.
+	var writer io.Writer = f.headerBuf
+	if !f.headerCompressionDisabled {
+		writer = f.headerCompressor
+	}
+	if _, err = writeHeaderValueBlock(writer, frame.Headers); err != nil {
+		return
+	}
+	if !f.headerCompressionDisabled {
+		f.headerCompressor.Flush()
+	}
+
+	// Set ControlFrameHeader
+	frame.CFHeader.version = Version
+	frame.CFHeader.frameType = TypeSynStream
+	frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 10)
+
+	// Serialize frame to Writer
+	if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
+		return err
+	}
+	if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
+		return err
+	}
+	if err = binary.Write(f.w, binary.BigEndian, frame.AssociatedToStreamId); err != nil {
+		return err
+	}
+	if err = binary.Write(f.w, binary.BigEndian, frame.Priority<<14); err != nil {
+		return err
+	}
+	if _, err = f.w.Write(f.headerBuf.Bytes()); err != nil {
+		return err
+	}
+	f.headerBuf.Reset()
+	return nil
+}
+
+func (f *Framer) writeSynReplyFrame(frame *SynReplyFrame) (err error) {
+	// Marshal the headers.
+	var writer io.Writer = f.headerBuf
+	if !f.headerCompressionDisabled {
+		writer = f.headerCompressor
+	}
+	if _, err = writeHeaderValueBlock(writer, frame.Headers); err != nil {
+		return
+	}
+	if !f.headerCompressionDisabled {
+		f.headerCompressor.Flush()
+	}
+
+	// Set ControlFrameHeader
+	frame.CFHeader.version = Version
+	frame.CFHeader.frameType = TypeSynReply
+	frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 6)
+
+	// Serialize frame to Writer
+	if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
+		return
+	}
+	if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
+		return
+	}
+	if err = binary.Write(f.w, binary.BigEndian, uint16(0)); err != nil {
+		return
+	}
+	if _, err = f.w.Write(f.headerBuf.Bytes()); err != nil {
+		return
+	}
+	f.headerBuf.Reset()
+	return
+}
+
+func (f *Framer) writeHeadersFrame(frame *HeadersFrame) (err error) {
+	// Marshal the headers.
+	var writer io.Writer = f.headerBuf
+	if !f.headerCompressionDisabled {
+		writer = f.headerCompressor
+	}
+	if _, err = writeHeaderValueBlock(writer, frame.Headers); err != nil {
+		return
+	}
+	if !f.headerCompressionDisabled {
+		f.headerCompressor.Flush()
+	}
+
+	// Set ControlFrameHeader
+	frame.CFHeader.version = Version
+	frame.CFHeader.frameType = TypeHeaders
+	frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 6)
+
+	// Serialize frame to Writer
+	if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
+		return
+	}
+	if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
+		return
+	}
+	if err = binary.Write(f.w, binary.BigEndian, uint16(0)); err != nil {
+		return
+	}
+	if _, err = f.w.Write(f.headerBuf.Bytes()); err != nil {
+		return
+	}
+	f.headerBuf.Reset()
+	return
+}
+
+func (f *Framer) writeDataFrame(frame *DataFrame) (err error) {
+	// Validate DataFrame
+	if frame.StreamId&0x80000000 != 0 || len(frame.Data) >= 0x0f000000 {
+		return &Error{InvalidDataFrame, frame.StreamId}
+	}
+
+	// Serialize frame to Writer
+	if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
+		return
+	}
+	flagsAndLength := (uint32(frame.Flags) << 24) | uint32(len(frame.Data))
+	if err = binary.Write(f.w, binary.BigEndian, flagsAndLength); err != nil {
+		return
+	}
+	if _, err = f.w.Write(frame.Data); err != nil {
+		return
+	}
+
+	return nil
+}

+ 137 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/websocket/client.go

@@ -0,0 +1,137 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package websocket
+
+import (
+	"bufio"
+	"crypto/tls"
+	"io"
+	"net"
+	"net/url"
+)
+
+// DialError is an error that occurs while dialling a websocket server.
+type DialError struct {
+	*Config
+	Err error
+}
+
+func (e *DialError) Error() string {
+	return "websocket.Dial " + e.Config.Location.String() + ": " + e.Err.Error()
+}
+
+// NewConfig creates a new WebSocket config for client connection.
+func NewConfig(server, origin string) (config *Config, err error) {
+	config = new(Config)
+	config.Version = ProtocolVersionHybi13
+	config.Location, err = url.ParseRequestURI(server)
+	if err != nil {
+		return
+	}
+	config.Origin, err = url.ParseRequestURI(origin)
+	if err != nil {
+		return
+	}
+	return
+}
+
+// NewClient creates a new WebSocket client connection over rwc.
+func NewClient(config *Config, rwc io.ReadWriteCloser) (ws *Conn, err error) {
+	br := bufio.NewReader(rwc)
+	bw := bufio.NewWriter(rwc)
+	switch config.Version {
+	case ProtocolVersionHixie75:
+		err = hixie75ClientHandshake(config, br, bw)
+	case ProtocolVersionHixie76, ProtocolVersionHybi00:
+		err = hixie76ClientHandshake(config, br, bw)
+	case ProtocolVersionHybi08, ProtocolVersionHybi13:
+		err = hybiClientHandshake(config, br, bw)
+	default:
+		err = ErrBadProtocolVersion
+	}
+	if err != nil {
+		return
+	}
+	buf := bufio.NewReadWriter(br, bw)
+	switch config.Version {
+	case ProtocolVersionHixie75, ProtocolVersionHixie76, ProtocolVersionHybi00:
+		ws = newHixieClientConn(config, buf, rwc)
+	case ProtocolVersionHybi08, ProtocolVersionHybi13:
+		ws = newHybiClientConn(config, buf, rwc)
+	}
+	return
+}
+
+/*
+Dial opens a new client connection to a WebSocket.
+
+A trivial example client:
+
+	package main
+
+	import (
+		"log"
+		"net/http"
+		"strings"
+		"websocket"
+	)
+
+	func main() {
+		origin := "http://localhost/"
+		url := "ws://localhost/ws" 
+		ws, err := websocket.Dial(url, "", origin)
+		if err != nil {
+			log.Fatal(err)
+		}
+		if _, err := ws.Write([]byte("hello, world!\n")); err != nil {
+			log.Fatal(err)
+		}
+		var msg = make([]byte, 512);
+		if n, err := ws.Read(msg); err != nil {
+			log.Fatal(err)
+		}
+		// use msg[0:n]
+	}
+*/
+func Dial(url_, protocol, origin string) (ws *Conn, err error) {
+	config, err := NewConfig(url_, origin)
+	if err != nil {
+		return nil, err
+	}
+	return DialConfig(config)
+}
+
+// DialConfig opens a new client connection to a WebSocket with a config.
+func DialConfig(config *Config) (ws *Conn, err error) {
+	var client net.Conn
+	if config.Location == nil {
+		return nil, &DialError{config, ErrBadWebSocketLocation}
+	}
+	if config.Origin == nil {
+		return nil, &DialError{config, ErrBadWebSocketOrigin}
+	}
+	switch config.Location.Scheme {
+	case "ws":
+		client, err = net.Dial("tcp", config.Location.Host)
+
+	case "wss":
+		client, err = tls.Dial("tcp", config.Location.Host, config.TlsConfig)
+
+	default:
+		err = ErrBadScheme
+	}
+	if err != nil {
+		goto Error
+	}
+
+	ws, err = NewClient(config, client)
+	if err != nil {
+		goto Error
+	}
+	return
+
+Error:
+	return nil, &DialError{config, err}
+}

+ 695 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/websocket/hixie.go

@@ -0,0 +1,695 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package websocket
+
+// This file implements a protocol of Hixie draft version 75 and 76
+// (draft 76 equals to hybi 00)
+
+import (
+	"bufio"
+	"bytes"
+	"crypto/md5"
+	"encoding/binary"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"math/rand"
+	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
+)
+
+// An aray of characters to be randomly inserted to construct Sec-WebSocket-Key
+// value. It holds characters from ranges U+0021 to U+002F and U+003A to U+007E.
+// See Step 21 in Section 4.1 Opening handshake.
+// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00#page-22
+var secKeyRandomChars [0x30 - 0x21 + 0x7F - 0x3A]byte
+
+func init() {
+	i := 0
+	for ch := byte(0x21); ch < 0x30; ch++ {
+		secKeyRandomChars[i] = ch
+		i++
+	}
+	for ch := byte(0x3a); ch < 0x7F; ch++ {
+		secKeyRandomChars[i] = ch
+		i++
+	}
+}
+
+type byteReader interface {
+	ReadByte() (byte, error)
+}
+
+// readHixieLength reads frame length for frame type 0x80-0xFF
+// as defined in Hixie draft.
+// See section 4.2 Data framing.
+// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00#section-4.2
+func readHixieLength(r byteReader) (length int64, lengthFields []byte, err error) {
+	for {
+		c, err := r.ReadByte()
+		if err != nil {
+			return 0, nil, err
+		}
+		lengthFields = append(lengthFields, c)
+		length = length*128 + int64(c&0x7f)
+		if c&0x80 == 0 {
+			break
+		}
+	}
+	return
+}
+
+// A hixieLengthFrameReader is a reader for frame type 0x80-0xFF
+// as defined in hixie draft.
+type hixieLengthFrameReader struct {
+	reader    io.Reader
+	FrameType byte
+	Length    int64
+	header    *bytes.Buffer
+	length    int
+}
+
+func (frame *hixieLengthFrameReader) Read(msg []byte) (n int, err error) {
+	return frame.reader.Read(msg)
+}
+
+func (frame *hixieLengthFrameReader) PayloadType() byte {
+	if frame.FrameType == '\xff' && frame.Length == 0 {
+		return CloseFrame
+	}
+	return UnknownFrame
+}
+
+func (frame *hixieLengthFrameReader) HeaderReader() io.Reader {
+	if frame.header == nil {
+		return nil
+	}
+	if frame.header.Len() == 0 {
+		frame.header = nil
+		return nil
+	}
+	return frame.header
+}
+
+func (frame *hixieLengthFrameReader) TrailerReader() io.Reader { return nil }
+
+func (frame *hixieLengthFrameReader) Len() (n int) { return frame.length }
+
+// A HixieSentinelFrameReader is a reader for frame type 0x00-0x7F
+// as defined in hixie draft.
+type hixieSentinelFrameReader struct {
+	reader      *bufio.Reader
+	FrameType   byte
+	header      *bytes.Buffer
+	data        []byte
+	seenTrailer bool
+	trailer     *bytes.Buffer
+}
+
+func (frame *hixieSentinelFrameReader) Read(msg []byte) (n int, err error) {
+	if len(frame.data) == 0 {
+		if frame.seenTrailer {
+			return 0, io.EOF
+		}
+		frame.data, err = frame.reader.ReadSlice('\xff')
+		if err == nil {
+			frame.seenTrailer = true
+			frame.data = frame.data[:len(frame.data)-1] // trim \xff
+			frame.trailer = bytes.NewBuffer([]byte{0xff})
+		}
+	}
+	n = copy(msg, frame.data)
+	frame.data = frame.data[n:]
+	return n, err
+}
+
+func (frame *hixieSentinelFrameReader) PayloadType() byte {
+	if frame.FrameType == 0 {
+		return TextFrame
+	}
+	return UnknownFrame
+}
+
+func (frame *hixieSentinelFrameReader) HeaderReader() io.Reader {
+	if frame.header == nil {
+		return nil
+	}
+	if frame.header.Len() == 0 {
+		frame.header = nil
+		return nil
+	}
+	return frame.header
+}
+
+func (frame *hixieSentinelFrameReader) TrailerReader() io.Reader {
+	if frame.trailer == nil {
+		return nil
+	}
+	if frame.trailer.Len() == 0 {
+		frame.trailer = nil
+		return nil
+	}
+	return frame.trailer
+}
+
+func (frame *hixieSentinelFrameReader) Len() int { return -1 }
+
+// A HixieFrameReaderFactory creates new frame reader based on its frame type.
+type hixieFrameReaderFactory struct {
+	*bufio.Reader
+}
+
+func (buf hixieFrameReaderFactory) NewFrameReader() (r frameReader, err error) {
+	var header []byte
+	var b byte
+	b, err = buf.ReadByte()
+	if err != nil {
+		return
+	}
+	header = append(header, b)
+	if b&0x80 == 0x80 {
+		length, lengthFields, err := readHixieLength(buf.Reader)
+		if err != nil {
+			return nil, err
+		}
+		if length == 0 {
+			return nil, io.EOF
+		}
+		header = append(header, lengthFields...)
+		return &hixieLengthFrameReader{
+			reader:    io.LimitReader(buf.Reader, length),
+			FrameType: b,
+			Length:    length,
+			header:    bytes.NewBuffer(header)}, err
+	}
+	return &hixieSentinelFrameReader{
+		reader:    buf.Reader,
+		FrameType: b,
+		header:    bytes.NewBuffer(header)}, err
+}
+
+type hixiFrameWriter struct {
+	writer *bufio.Writer
+}
+
+func (frame *hixiFrameWriter) Write(msg []byte) (n int, err error) {
+	frame.writer.WriteByte(0)
+	frame.writer.Write(msg)
+	frame.writer.WriteByte(0xff)
+	err = frame.writer.Flush()
+	return len(msg), err
+}
+
+func (frame *hixiFrameWriter) Close() error { return nil }
+
+type hixiFrameWriterFactory struct {
+	*bufio.Writer
+}
+
+func (buf hixiFrameWriterFactory) NewFrameWriter(payloadType byte) (frame frameWriter, err error) {
+	if payloadType != TextFrame {
+		return nil, ErrNotSupported
+	}
+	return &hixiFrameWriter{writer: buf.Writer}, nil
+}
+
+type hixiFrameHandler struct {
+	conn *Conn
+}
+
+func (handler *hixiFrameHandler) HandleFrame(frame frameReader) (r frameReader, err error) {
+	if header := frame.HeaderReader(); header != nil {
+		io.Copy(ioutil.Discard, header)
+	}
+	if frame.PayloadType() != TextFrame {
+		io.Copy(ioutil.Discard, frame)
+		return nil, nil
+	}
+	return frame, nil
+}
+
+func (handler *hixiFrameHandler) WriteClose(_ int) (err error) {
+	handler.conn.wio.Lock()
+	defer handler.conn.wio.Unlock()
+	closingFrame := []byte{'\xff', '\x00'}
+	handler.conn.buf.Write(closingFrame)
+	return handler.conn.buf.Flush()
+}
+
+// newHixiConn creates a new WebSocket connection speaking hixie draft protocol.
+func newHixieConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
+	if buf == nil {
+		br := bufio.NewReader(rwc)
+		bw := bufio.NewWriter(rwc)
+		buf = bufio.NewReadWriter(br, bw)
+	}
+	ws := &Conn{config: config, request: request, buf: buf, rwc: rwc,
+		frameReaderFactory: hixieFrameReaderFactory{buf.Reader},
+		frameWriterFactory: hixiFrameWriterFactory{buf.Writer},
+		PayloadType:        TextFrame}
+	ws.frameHandler = &hixiFrameHandler{ws}
+	return ws
+}
+
+// getChallengeResponse computes the expected response from the
+// challenge as described in section 5.1 Opening Handshake steps 42 to
+// 43 of http://www.whatwg.org/specs/web-socket-protocol/
+func getChallengeResponse(number1, number2 uint32, key3 []byte) (expected []byte, err error) {
+	// 41. Let /challenge/ be the concatenation of /number_1/, expressed
+	// a big-endian 32 bit integer, /number_2/, expressed in a big-
+	// endian 32 bit integer, and the eight bytes of /key_3/ in the
+	// order they were sent to the wire.
+	challenge := make([]byte, 16)
+	binary.BigEndian.PutUint32(challenge[0:], number1)
+	binary.BigEndian.PutUint32(challenge[4:], number2)
+	copy(challenge[8:], key3)
+
+	// 42. Let /expected/ be the MD5 fingerprint of /challenge/ as a big-
+	// endian 128 bit string.
+	h := md5.New()
+	if _, err = h.Write(challenge); err != nil {
+		return
+	}
+	expected = h.Sum(nil)
+	return
+}
+
+// Generates handshake key as described in 4.1 Opening handshake step 16 to 22.
+// cf. http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
+func generateKeyNumber() (key string, number uint32) {
+	// 16.  Let /spaces_n/ be a random integer from 1 to 12 inclusive.
+	spaces := rand.Intn(12) + 1
+
+	// 17. Let /max_n/ be the largest integer not greater than
+	//     4,294,967,295 divided by /spaces_n/
+	max := int(4294967295 / uint32(spaces))
+
+	// 18. Let /number_n/ be a random integer from 0 to /max_n/ inclusive.
+	number = uint32(rand.Intn(max + 1))
+
+	// 19. Let /product_n/ be the result of multiplying /number_n/ and
+	//     /spaces_n/ together.
+	product := number * uint32(spaces)
+
+	// 20. Let /key_n/ be a string consisting of /product_n/, expressed
+	// in base ten using the numerals in the range U+0030 DIGIT ZERO (0)
+	// to U+0039 DIGIT NINE (9).
+	key = fmt.Sprintf("%d", product)
+
+	// 21. Insert between one and twelve random characters from the ranges
+	//     U+0021 to U+002F and U+003A to U+007E into /key_n/ at random
+	//     positions.
+	n := rand.Intn(12) + 1
+	for i := 0; i < n; i++ {
+		pos := rand.Intn(len(key)) + 1
+		ch := secKeyRandomChars[rand.Intn(len(secKeyRandomChars))]
+		key = key[0:pos] + string(ch) + key[pos:]
+	}
+
+	// 22. Insert /spaces_n/ U+0020 SPACE characters into /key_n/ at random
+	//     positions other than the start or end of the string.
+	for i := 0; i < spaces; i++ {
+		pos := rand.Intn(len(key)-1) + 1
+		key = key[0:pos] + " " + key[pos:]
+	}
+
+	return
+}
+
+// Generates handshake key_3 as described in 4.1 Opening handshake step 26.
+// cf. http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
+func generateKey3() (key []byte) {
+	// 26. Let /key3/ be a string consisting of eight random bytes (or
+	//  equivalently, a random 64 bit integer encoded in big-endian order).
+	key = make([]byte, 8)
+	for i := 0; i < 8; i++ {
+		key[i] = byte(rand.Intn(256))
+	}
+	return
+}
+
+// Cilent handhake described in (soon obsolete)
+// draft-ietf-hybi-thewebsocket-protocol-00
+// (draft-hixie-thewebsocket-protocol-76) 
+func hixie76ClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) {
+	switch config.Version {
+	case ProtocolVersionHixie76, ProtocolVersionHybi00:
+	default:
+		panic("wrong protocol version.")
+	}
+	// 4.1. Opening handshake.
+	// Step 5.  send a request line.
+	bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")
+
+	// Step 6-14. push request headers in fields.
+	fields := []string{
+		"Upgrade: WebSocket\r\n",
+		"Connection: Upgrade\r\n",
+		"Host: " + config.Location.Host + "\r\n",
+		"Origin: " + config.Origin.String() + "\r\n",
+	}
+	if len(config.Protocol) > 0 {
+		if len(config.Protocol) != 1 {
+			return ErrBadWebSocketProtocol
+		}
+		fields = append(fields, "Sec-WebSocket-Protocol: "+config.Protocol[0]+"\r\n")
+	}
+	// TODO(ukai): Step 15. send cookie if any.
+
+	// Step 16-23. generate keys and push Sec-WebSocket-Key<n> in fields.
+	key1, number1 := generateKeyNumber()
+	key2, number2 := generateKeyNumber()
+	if config.handshakeData != nil {
+		key1 = config.handshakeData["key1"]
+		n, err := strconv.ParseUint(config.handshakeData["number1"], 10, 32)
+		if err != nil {
+			panic(err)
+		}
+		number1 = uint32(n)
+		key2 = config.handshakeData["key2"]
+		n, err = strconv.ParseUint(config.handshakeData["number2"], 10, 32)
+		if err != nil {
+			panic(err)
+		}
+		number2 = uint32(n)
+	}
+	fields = append(fields, "Sec-WebSocket-Key1: "+key1+"\r\n")
+	fields = append(fields, "Sec-WebSocket-Key2: "+key2+"\r\n")
+
+	// Step 24. shuffle fields and send them out.
+	for i := 1; i < len(fields); i++ {
+		j := rand.Intn(i)
+		fields[i], fields[j] = fields[j], fields[i]
+	}
+	for i := 0; i < len(fields); i++ {
+		bw.WriteString(fields[i])
+	}
+	// Step 25. send CRLF.
+	bw.WriteString("\r\n")
+
+	// Step 26. generate 8 bytes random key.
+	key3 := generateKey3()
+	if config.handshakeData != nil {
+		key3 = []byte(config.handshakeData["key3"])
+	}
+	// Step 27. send it out.
+	bw.Write(key3)
+	if err = bw.Flush(); err != nil {
+		return
+	}
+
+	// Step 28-29, 32-40. read response from server.
+	resp, err := http.ReadResponse(br, &http.Request{Method: "GET"})
+	if err != nil {
+		return err
+	}
+	// Step 30. check response code is 101.
+	if resp.StatusCode != 101 {
+		return ErrBadStatus
+	}
+
+	// Step 41. check websocket headers.
+	if resp.Header.Get("Upgrade") != "WebSocket" ||
+		strings.ToLower(resp.Header.Get("Connection")) != "upgrade" {
+		return ErrBadUpgrade
+	}
+
+	if resp.Header.Get("Sec-Websocket-Origin") != config.Origin.String() {
+		return ErrBadWebSocketOrigin
+	}
+
+	if resp.Header.Get("Sec-Websocket-Location") != config.Location.String() {
+		return ErrBadWebSocketLocation
+	}
+
+	if len(config.Protocol) > 0 && resp.Header.Get("Sec-Websocket-Protocol") != config.Protocol[0] {
+		return ErrBadWebSocketProtocol
+	}
+
+	// Step 42-43. get expected data from challenge data.
+	expected, err := getChallengeResponse(number1, number2, key3)
+	if err != nil {
+		return err
+	}
+
+	// Step 44. read 16 bytes from server.
+	reply := make([]byte, 16)
+	if _, err = io.ReadFull(br, reply); err != nil {
+		return err
+	}
+
+	// Step 45. check the reply equals to expected data.
+	if !bytes.Equal(expected, reply) {
+		return ErrChallengeResponse
+	}
+	// WebSocket connection is established.
+	return
+}
+
+// Client Handshake described in (soon obsolete)
+// draft-hixie-thewebsocket-protocol-75.
+func hixie75ClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) {
+	if config.Version != ProtocolVersionHixie75 {
+		panic("wrong protocol version.")
+	}
+	bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")
+	bw.WriteString("Upgrade: WebSocket\r\n")
+	bw.WriteString("Connection: Upgrade\r\n")
+	bw.WriteString("Host: " + config.Location.Host + "\r\n")
+	bw.WriteString("Origin: " + config.Origin.String() + "\r\n")
+	if len(config.Protocol) > 0 {
+		if len(config.Protocol) != 1 {
+			return ErrBadWebSocketProtocol
+		}
+		bw.WriteString("WebSocket-Protocol: " + config.Protocol[0] + "\r\n")
+	}
+	bw.WriteString("\r\n")
+	bw.Flush()
+	resp, err := http.ReadResponse(br, &http.Request{Method: "GET"})
+	if err != nil {
+		return
+	}
+	if resp.Status != "101 Web Socket Protocol Handshake" {
+		return ErrBadStatus
+	}
+	if resp.Header.Get("Upgrade") != "WebSocket" ||
+		resp.Header.Get("Connection") != "Upgrade" {
+		return ErrBadUpgrade
+	}
+	if resp.Header.Get("Websocket-Origin") != config.Origin.String() {
+		return ErrBadWebSocketOrigin
+	}
+	if resp.Header.Get("Websocket-Location") != config.Location.String() {
+		return ErrBadWebSocketLocation
+	}
+	if len(config.Protocol) > 0 && resp.Header.Get("Websocket-Protocol") != config.Protocol[0] {
+		return ErrBadWebSocketProtocol
+	}
+	return
+}
+
+// newHixieClientConn returns new WebSocket connection speaking hixie draft protocol.
+func newHixieClientConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser) *Conn {
+	return newHixieConn(config, buf, rwc, nil)
+}
+
+// Gets key number from Sec-WebSocket-Key<n>: field as described
+// in 5.2 Sending the server's opening handshake, 4.
+func getKeyNumber(s string) (r uint32) {
+	// 4. Let /key-number_n/ be the digits (characters in the range
+	// U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9)) in /key_1/,
+	// interpreted as a base ten integer, ignoring all other characters
+	// in /key_n/.
+	r = 0
+	for i := 0; i < len(s); i++ {
+		if s[i] >= '0' && s[i] <= '9' {
+			r = r*10 + uint32(s[i]) - '0'
+		}
+	}
+	return
+}
+
+// A Hixie76ServerHandshaker performs a server handshake using
+// hixie draft 76 protocol.
+type hixie76ServerHandshaker struct {
+	*Config
+	challengeResponse []byte
+}
+
+func (c *hixie76ServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) {
+	c.Version = ProtocolVersionHybi00
+	if req.Method != "GET" {
+		return http.StatusMethodNotAllowed, ErrBadRequestMethod
+	}
+	// HTTP version can be safely ignored.
+
+	if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" ||
+		strings.ToLower(req.Header.Get("Connection")) != "upgrade" {
+		return http.StatusBadRequest, ErrNotWebSocket
+	}
+
+	// TODO(ukai): check Host
+	c.Origin, err = url.ParseRequestURI(req.Header.Get("Origin"))
+	if err != nil {
+		return http.StatusBadRequest, err
+	}
+
+	key1 := req.Header.Get("Sec-Websocket-Key1")
+	if key1 == "" {
+		return http.StatusBadRequest, ErrChallengeResponse
+	}
+	key2 := req.Header.Get("Sec-Websocket-Key2")
+	if key2 == "" {
+		return http.StatusBadRequest, ErrChallengeResponse
+	}
+	key3 := make([]byte, 8)
+	if _, err := io.ReadFull(buf, key3); err != nil {
+		return http.StatusBadRequest, ErrChallengeResponse
+	}
+
+	var scheme string
+	if req.TLS != nil {
+		scheme = "wss"
+	} else {
+		scheme = "ws"
+	}
+	c.Location, err = url.ParseRequestURI(scheme + "://" + req.Host + req.URL.RequestURI())
+	if err != nil {
+		return http.StatusBadRequest, err
+	}
+
+	// Step 4. get key number in Sec-WebSocket-Key<n> fields.
+	keyNumber1 := getKeyNumber(key1)
+	keyNumber2 := getKeyNumber(key2)
+
+	// Step 5. get number of spaces in Sec-WebSocket-Key<n> fields.
+	space1 := uint32(strings.Count(key1, " "))
+	space2 := uint32(strings.Count(key2, " "))
+	if space1 == 0 || space2 == 0 {
+		return http.StatusBadRequest, ErrChallengeResponse
+	}
+
+	// Step 6. key number must be an integral multiple of spaces.
+	if keyNumber1%space1 != 0 || keyNumber2%space2 != 0 {
+		return http.StatusBadRequest, ErrChallengeResponse
+	}
+
+	// Step 7. let part be key number divided by spaces.
+	part1 := keyNumber1 / space1
+	part2 := keyNumber2 / space2
+
+	// Step 8. let challenge be concatenation of part1, part2 and key3.
+	// Step 9. get MD5 fingerprint of challenge.
+	c.challengeResponse, err = getChallengeResponse(part1, part2, key3)
+	if err != nil {
+		return http.StatusInternalServerError, err
+	}
+	protocol := strings.TrimSpace(req.Header.Get("Sec-Websocket-Protocol"))
+	protocols := strings.Split(protocol, ",")
+	for i := 0; i < len(protocols); i++ {
+		c.Protocol = append(c.Protocol, strings.TrimSpace(protocols[i]))
+	}
+
+	return http.StatusSwitchingProtocols, nil
+}
+
+func (c *hixie76ServerHandshaker) AcceptHandshake(buf *bufio.Writer) (err error) {
+	if len(c.Protocol) > 0 {
+		if len(c.Protocol) != 1 {
+			return ErrBadWebSocketProtocol
+		}
+	}
+
+	// Step 10. send response status line.
+	buf.WriteString("HTTP/1.1 101 WebSocket Protocol Handshake\r\n")
+	// Step 11. send response headers.
+	buf.WriteString("Upgrade: WebSocket\r\n")
+	buf.WriteString("Connection: Upgrade\r\n")
+	buf.WriteString("Sec-WebSocket-Origin: " + c.Origin.String() + "\r\n")
+	buf.WriteString("Sec-WebSocket-Location: " + c.Location.String() + "\r\n")
+	if len(c.Protocol) > 0 {
+		buf.WriteString("Sec-WebSocket-Protocol: " + c.Protocol[0] + "\r\n")
+	}
+	// Step 12. send CRLF.
+	buf.WriteString("\r\n")
+	// Step 13. send response data.
+	buf.Write(c.challengeResponse)
+	return buf.Flush()
+}
+
+func (c *hixie76ServerHandshaker) NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) (conn *Conn) {
+	return newHixieServerConn(c.Config, buf, rwc, request)
+}
+
+// A hixie75ServerHandshaker performs a server handshake using
+// hixie draft 75 protocol.
+type hixie75ServerHandshaker struct {
+	*Config
+}
+
+func (c *hixie75ServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) {
+	c.Version = ProtocolVersionHixie75
+	if req.Method != "GET" || req.Proto != "HTTP/1.1" {
+		return http.StatusMethodNotAllowed, ErrBadRequestMethod
+	}
+	if req.Header.Get("Upgrade") != "WebSocket" {
+		return http.StatusBadRequest, ErrNotWebSocket
+	}
+	if req.Header.Get("Connection") != "Upgrade" {
+		return http.StatusBadRequest, ErrNotWebSocket
+	}
+	c.Origin, err = url.ParseRequestURI(strings.TrimSpace(req.Header.Get("Origin")))
+	if err != nil {
+		return http.StatusBadRequest, err
+	}
+
+	var scheme string
+	if req.TLS != nil {
+		scheme = "wss"
+	} else {
+		scheme = "ws"
+	}
+	c.Location, err = url.ParseRequestURI(scheme + "://" + req.Host + req.URL.RequestURI())
+	if err != nil {
+		return http.StatusBadRequest, err
+	}
+	protocol := strings.TrimSpace(req.Header.Get("Websocket-Protocol"))
+	protocols := strings.Split(protocol, ",")
+	for i := 0; i < len(protocols); i++ {
+		c.Protocol = append(c.Protocol, strings.TrimSpace(protocols[i]))
+	}
+
+	return http.StatusSwitchingProtocols, nil
+}
+
+func (c *hixie75ServerHandshaker) AcceptHandshake(buf *bufio.Writer) (err error) {
+	if len(c.Protocol) > 0 {
+		if len(c.Protocol) != 1 {
+			return ErrBadWebSocketProtocol
+		}
+	}
+
+	buf.WriteString("HTTP/1.1 101 Web Socket Protocol Handshake\r\n")
+	buf.WriteString("Upgrade: WebSocket\r\n")
+	buf.WriteString("Connection: Upgrade\r\n")
+	buf.WriteString("WebSocket-Origin: " + c.Origin.String() + "\r\n")
+	buf.WriteString("WebSocket-Location: " + c.Location.String() + "\r\n")
+	if len(c.Protocol) > 0 {
+		buf.WriteString("WebSocket-Protocol: " + c.Protocol[0] + "\r\n")
+	}
+	buf.WriteString("\r\n")
+	return buf.Flush()
+}
+
+func (c *hixie75ServerHandshaker) NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) (conn *Conn) {
+	return newHixieServerConn(c.Config, buf, rwc, request)
+}
+
+// newHixieServerConn returns a new WebSocket connection speaking hixie draft protocol.
+func newHixieServerConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
+	return newHixieConn(config, buf, rwc, request)
+}

+ 201 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/websocket/hixie_test.go

@@ -0,0 +1,201 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package websocket
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"strings"
+	"testing"
+)
+
+// Test the getChallengeResponse function with values from section
+// 5.1 of the specification steps 18, 26, and 43 from
+// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
+func TestHixie76Challenge(t *testing.T) {
+	var part1 uint32 = 777007543
+	var part2 uint32 = 114997259
+	key3 := []byte{0x47, 0x30, 0x22, 0x2D, 0x5A, 0x3F, 0x47, 0x58}
+	expected := []byte("0st3Rl&q-2ZU^weu")
+
+	response, err := getChallengeResponse(part1, part2, key3)
+	if err != nil {
+		t.Errorf("getChallengeResponse: returned error %v", err)
+		return
+	}
+	if !bytes.Equal(expected, response) {
+		t.Errorf("getChallengeResponse: expected %q got %q", expected, response)
+	}
+}
+
+func TestHixie76ClientHandshake(t *testing.T) {
+	b := bytes.NewBuffer([]byte{})
+	bw := bufio.NewWriter(b)
+	br := bufio.NewReader(strings.NewReader(`HTTP/1.1 101 WebSocket Protocol Handshake
+Upgrade: WebSocket
+Connection: Upgrade
+Sec-WebSocket-Origin: http://example.com
+Sec-WebSocket-Location: ws://example.com/demo
+Sec-WebSocket-Protocol: sample
+
+8jKS'y:G*Co,Wxa-`))
+
+	var err error
+	config := new(Config)
+	config.Location, err = url.ParseRequestURI("ws://example.com/demo")
+	if err != nil {
+		t.Fatal("location url", err)
+	}
+	config.Origin, err = url.ParseRequestURI("http://example.com")
+	if err != nil {
+		t.Fatal("origin url", err)
+	}
+	config.Protocol = append(config.Protocol, "sample")
+	config.Version = ProtocolVersionHixie76
+
+	config.handshakeData = map[string]string{
+		"key1":    "4 @1  46546xW%0l 1 5",
+		"number1": "829309203",
+		"key2":    "12998 5 Y3 1  .P00",
+		"number2": "259970620",
+		"key3":    "^n:ds[4U",
+	}
+	err = hixie76ClientHandshake(config, br, bw)
+	if err != nil {
+		t.Errorf("handshake failed: %v", err)
+	}
+	req, err := http.ReadRequest(bufio.NewReader(b))
+	if err != nil {
+		t.Fatalf("read request: %v", err)
+	}
+	if req.Method != "GET" {
+		t.Errorf("request method expected GET, but got %q", req.Method)
+	}
+	if req.URL.Path != "/demo" {
+		t.Errorf("request path expected /demo, but got %q", req.URL.Path)
+	}
+	if req.Proto != "HTTP/1.1" {
+		t.Errorf("request proto expected HTTP/1.1, but got %q", req.Proto)
+	}
+	if req.Host != "example.com" {
+		t.Errorf("request Host expected example.com, but got %v", req.Host)
+	}
+	var expectedHeader = map[string]string{
+		"Connection":             "Upgrade",
+		"Upgrade":                "WebSocket",
+		"Origin":                 "http://example.com",
+		"Sec-Websocket-Key1":     config.handshakeData["key1"],
+		"Sec-Websocket-Key2":     config.handshakeData["key2"],
+		"Sec-WebSocket-Protocol": config.Protocol[0],
+	}
+	for k, v := range expectedHeader {
+		if req.Header.Get(k) != v {
+			t.Errorf(fmt.Sprintf("%s expected %q but got %q", k, v, req.Header.Get(k)))
+		}
+	}
+}
+
+func TestHixie76ServerHandshake(t *testing.T) {
+	config := new(Config)
+	handshaker := &hixie76ServerHandshaker{Config: config}
+	br := bufio.NewReader(strings.NewReader(`GET /demo HTTP/1.1
+Host: example.com
+Connection: Upgrade
+Sec-WebSocket-Key2: 12998 5 Y3 1  .P00
+Sec-WebSocket-Protocol: sample
+Upgrade: WebSocket
+Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5
+Origin: http://example.com
+
+^n:ds[4U`))
+	req, err := http.ReadRequest(br)
+	if err != nil {
+		t.Fatal("request", err)
+	}
+	code, err := handshaker.ReadHandshake(br, req)
+	if err != nil {
+		t.Errorf("handshake failed: %v", err)
+	}
+	if code != http.StatusSwitchingProtocols {
+		t.Errorf("status expected %q but got %q", http.StatusSwitchingProtocols, code)
+	}
+	b := bytes.NewBuffer([]byte{})
+	bw := bufio.NewWriter(b)
+
+	err = handshaker.AcceptHandshake(bw)
+	if err != nil {
+		t.Errorf("handshake response failed: %v", err)
+	}
+	expectedResponse := strings.Join([]string{
+		"HTTP/1.1 101 WebSocket Protocol Handshake",
+		"Upgrade: WebSocket",
+		"Connection: Upgrade",
+		"Sec-WebSocket-Origin: http://example.com",
+		"Sec-WebSocket-Location: ws://example.com/demo",
+		"Sec-WebSocket-Protocol: sample",
+		"", ""}, "\r\n") + "8jKS'y:G*Co,Wxa-"
+	if b.String() != expectedResponse {
+		t.Errorf("handshake expected %q but got %q", expectedResponse, b.String())
+	}
+}
+
+func TestHixie76SkipLengthFrame(t *testing.T) {
+	b := []byte{'\x80', '\x01', 'x', 0, 'h', 'e', 'l', 'l', 'o', '\xff'}
+	buf := bytes.NewBuffer(b)
+	br := bufio.NewReader(buf)
+	bw := bufio.NewWriter(buf)
+	config := newConfig(t, "/")
+	ws := newHixieConn(config, bufio.NewReadWriter(br, bw), nil, nil)
+	msg := make([]byte, 5)
+	n, err := ws.Read(msg)
+	if err != nil {
+		t.Errorf("Read: %v", err)
+	}
+	if !bytes.Equal(b[4:9], msg[0:n]) {
+		t.Errorf("Read: expected %q got %q", b[4:9], msg[0:n])
+	}
+}
+
+func TestHixie76SkipNoUTF8Frame(t *testing.T) {
+	b := []byte{'\x01', 'n', '\xff', 0, 'h', 'e', 'l', 'l', 'o', '\xff'}
+	buf := bytes.NewBuffer(b)
+	br := bufio.NewReader(buf)
+	bw := bufio.NewWriter(buf)
+	config := newConfig(t, "/")
+	ws := newHixieConn(config, bufio.NewReadWriter(br, bw), nil, nil)
+	msg := make([]byte, 5)
+	n, err := ws.Read(msg)
+	if err != nil {
+		t.Errorf("Read: %v", err)
+	}
+	if !bytes.Equal(b[4:9], msg[0:n]) {
+		t.Errorf("Read: expected %q got %q", b[4:9], msg[0:n])
+	}
+}
+
+func TestHixie76ClosingFrame(t *testing.T) {
+	b := []byte{0, 'h', 'e', 'l', 'l', 'o', '\xff'}
+	buf := bytes.NewBuffer(b)
+	br := bufio.NewReader(buf)
+	bw := bufio.NewWriter(buf)
+	config := newConfig(t, "/")
+	ws := newHixieConn(config, bufio.NewReadWriter(br, bw), nil, nil)
+	msg := make([]byte, 5)
+	n, err := ws.Read(msg)
+	if err != nil {
+		t.Errorf("read: %v", err)
+	}
+	if !bytes.Equal(b[1:6], msg[0:n]) {
+		t.Errorf("Read: expected %q got %q", b[1:6], msg[0:n])
+	}
+	n, err = ws.Read(msg)
+	if err != io.EOF {
+		t.Errorf("read: %v", err)
+	}
+}

+ 549 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/websocket/hybi.go

@@ -0,0 +1,549 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package websocket
+
+// This file implements a protocol of hybi draft.
+// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
+
+import (
+	"bufio"
+	"bytes"
+	"crypto/rand"
+	"crypto/sha1"
+	"encoding/base64"
+	"encoding/binary"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"strings"
+)
+
+const (
+	websocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+
+	closeStatusNormal            = 1000
+	closeStatusGoingAway         = 1001
+	closeStatusProtocolError     = 1002
+	closeStatusUnsupportedData   = 1003
+	closeStatusFrameTooLarge     = 1004
+	closeStatusNoStatusRcvd      = 1005
+	closeStatusAbnormalClosure   = 1006
+	closeStatusBadMessageData    = 1007
+	closeStatusPolicyViolation   = 1008
+	closeStatusTooBigData        = 1009
+	closeStatusExtensionMismatch = 1010
+
+	maxControlFramePayloadLength = 125
+)
+
+var (
+	ErrBadMaskingKey         = &ProtocolError{"bad masking key"}
+	ErrBadPongMessage        = &ProtocolError{"bad pong message"}
+	ErrBadClosingStatus      = &ProtocolError{"bad closing status"}
+	ErrUnsupportedExtensions = &ProtocolError{"unsupported extensions"}
+	ErrNotImplemented        = &ProtocolError{"not implemented"}
+)
+
+// A hybiFrameHeader is a frame header as defined in hybi draft.
+type hybiFrameHeader struct {
+	Fin        bool
+	Rsv        [3]bool
+	OpCode     byte
+	Length     int64
+	MaskingKey []byte
+
+	data *bytes.Buffer
+}
+
+// A hybiFrameReader is a reader for hybi frame.
+type hybiFrameReader struct {
+	reader io.Reader
+
+	header hybiFrameHeader
+	pos    int64
+	length int
+}
+
+func (frame *hybiFrameReader) Read(msg []byte) (n int, err error) {
+	n, err = frame.reader.Read(msg)
+	if err != nil {
+		return 0, err
+	}
+	if frame.header.MaskingKey != nil {
+		for i := 0; i < n; i++ {
+			msg[i] = msg[i] ^ frame.header.MaskingKey[frame.pos%4]
+			frame.pos++
+		}
+	}
+	return n, err
+}
+
+func (frame *hybiFrameReader) PayloadType() byte { return frame.header.OpCode }
+
+func (frame *hybiFrameReader) HeaderReader() io.Reader {
+	if frame.header.data == nil {
+		return nil
+	}
+	if frame.header.data.Len() == 0 {
+		return nil
+	}
+	return frame.header.data
+}
+
+func (frame *hybiFrameReader) TrailerReader() io.Reader { return nil }
+
+func (frame *hybiFrameReader) Len() (n int) { return frame.length }
+
+// A hybiFrameReaderFactory creates new frame reader based on its frame type.
+type hybiFrameReaderFactory struct {
+	*bufio.Reader
+}
+
+// NewFrameReader reads a frame header from the connection, and creates new reader for the frame.
+// See Section 5.2 Base Frameing protocol for detail.
+// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17#section-5.2
+func (buf hybiFrameReaderFactory) NewFrameReader() (frame frameReader, err error) {
+	hybiFrame := new(hybiFrameReader)
+	frame = hybiFrame
+	var header []byte
+	var b byte
+	// First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits)
+	b, err = buf.ReadByte()
+	if err != nil {
+		return
+	}
+	header = append(header, b)
+	hybiFrame.header.Fin = ((header[0] >> 7) & 1) != 0
+	for i := 0; i < 3; i++ {
+		j := uint(6 - i)
+		hybiFrame.header.Rsv[i] = ((header[0] >> j) & 1) != 0
+	}
+	hybiFrame.header.OpCode = header[0] & 0x0f
+
+	// Second byte. Mask/Payload len(7bits)
+	b, err = buf.ReadByte()
+	if err != nil {
+		return
+	}
+	header = append(header, b)
+	mask := (b & 0x80) != 0
+	b &= 0x7f
+	lengthFields := 0
+	switch {
+	case b <= 125: // Payload length 7bits.
+		hybiFrame.header.Length = int64(b)
+	case b == 126: // Payload length 7+16bits
+		lengthFields = 2
+	case b == 127: // Payload length 7+64bits
+		lengthFields = 8
+	}
+	for i := 0; i < lengthFields; i++ {
+		b, err = buf.ReadByte()
+		if err != nil {
+			return
+		}
+		header = append(header, b)
+		hybiFrame.header.Length = hybiFrame.header.Length*256 + int64(b)
+	}
+	if mask {
+		// Masking key. 4 bytes.
+		for i := 0; i < 4; i++ {
+			b, err = buf.ReadByte()
+			if err != nil {
+				return
+			}
+			header = append(header, b)
+			hybiFrame.header.MaskingKey = append(hybiFrame.header.MaskingKey, b)
+		}
+	}
+	hybiFrame.reader = io.LimitReader(buf.Reader, hybiFrame.header.Length)
+	hybiFrame.header.data = bytes.NewBuffer(header)
+	hybiFrame.length = len(header) + int(hybiFrame.header.Length)
+	return
+}
+
+// A HybiFrameWriter is a writer for hybi frame.
+type hybiFrameWriter struct {
+	writer *bufio.Writer
+
+	header *hybiFrameHeader
+}
+
+func (frame *hybiFrameWriter) Write(msg []byte) (n int, err error) {
+	var header []byte
+	var b byte
+	if frame.header.Fin {
+		b |= 0x80
+	}
+	for i := 0; i < 3; i++ {
+		if frame.header.Rsv[i] {
+			j := uint(6 - i)
+			b |= 1 << j
+		}
+	}
+	b |= frame.header.OpCode
+	header = append(header, b)
+	if frame.header.MaskingKey != nil {
+		b = 0x80
+	} else {
+		b = 0
+	}
+	lengthFields := 0
+	length := len(msg)
+	switch {
+	case length <= 125:
+		b |= byte(length)
+	case length < 65536:
+		b |= 126
+		lengthFields = 2
+	default:
+		b |= 127
+		lengthFields = 8
+	}
+	header = append(header, b)
+	for i := 0; i < lengthFields; i++ {
+		j := uint((lengthFields - i - 1) * 8)
+		b = byte((length >> j) & 0xff)
+		header = append(header, b)
+	}
+	if frame.header.MaskingKey != nil {
+		if len(frame.header.MaskingKey) != 4 {
+			return 0, ErrBadMaskingKey
+		}
+		header = append(header, frame.header.MaskingKey...)
+		frame.writer.Write(header)
+		var data []byte
+
+		for i := 0; i < length; i++ {
+			data = append(data, msg[i]^frame.header.MaskingKey[i%4])
+		}
+		frame.writer.Write(data)
+		err = frame.writer.Flush()
+		return length, err
+	}
+	frame.writer.Write(header)
+	frame.writer.Write(msg)
+	err = frame.writer.Flush()
+	return length, err
+}
+
+func (frame *hybiFrameWriter) Close() error { return nil }
+
+type hybiFrameWriterFactory struct {
+	*bufio.Writer
+	needMaskingKey bool
+}
+
+func (buf hybiFrameWriterFactory) NewFrameWriter(payloadType byte) (frame frameWriter, err error) {
+	frameHeader := &hybiFrameHeader{Fin: true, OpCode: payloadType}
+	if buf.needMaskingKey {
+		frameHeader.MaskingKey, err = generateMaskingKey()
+		if err != nil {
+			return nil, err
+		}
+	}
+	return &hybiFrameWriter{writer: buf.Writer, header: frameHeader}, nil
+}
+
+type hybiFrameHandler struct {
+	conn        *Conn
+	payloadType byte
+}
+
+func (handler *hybiFrameHandler) HandleFrame(frame frameReader) (r frameReader, err error) {
+	if handler.conn.IsServerConn() {
+		// The client MUST mask all frames sent to the server.
+		if frame.(*hybiFrameReader).header.MaskingKey == nil {
+			handler.WriteClose(closeStatusProtocolError)
+			return nil, io.EOF
+		}
+	} else {
+		// The server MUST NOT mask all frames.
+		if frame.(*hybiFrameReader).header.MaskingKey != nil {
+			handler.WriteClose(closeStatusProtocolError)
+			return nil, io.EOF
+		}
+	}
+	if header := frame.HeaderReader(); header != nil {
+		io.Copy(ioutil.Discard, header)
+	}
+	switch frame.PayloadType() {
+	case ContinuationFrame:
+		frame.(*hybiFrameReader).header.OpCode = handler.payloadType
+	case TextFrame, BinaryFrame:
+		handler.payloadType = frame.PayloadType()
+	case CloseFrame:
+		return nil, io.EOF
+	case PingFrame:
+		pingMsg := make([]byte, maxControlFramePayloadLength)
+		n, err := io.ReadFull(frame, pingMsg)
+		if err != nil && err != io.ErrUnexpectedEOF {
+			return nil, err
+		}
+		io.Copy(ioutil.Discard, frame)
+		n, err = handler.WritePong(pingMsg[:n])
+		if err != nil {
+			return nil, err
+		}
+		return nil, nil
+	case PongFrame:
+		return nil, ErrNotImplemented
+	}
+	return frame, nil
+}
+
+func (handler *hybiFrameHandler) WriteClose(status int) (err error) {
+	handler.conn.wio.Lock()
+	defer handler.conn.wio.Unlock()
+	w, err := handler.conn.frameWriterFactory.NewFrameWriter(CloseFrame)
+	if err != nil {
+		return err
+	}
+	msg := make([]byte, 2)
+	binary.BigEndian.PutUint16(msg, uint16(status))
+	_, err = w.Write(msg)
+	w.Close()
+	return err
+}
+
+func (handler *hybiFrameHandler) WritePong(msg []byte) (n int, err error) {
+	handler.conn.wio.Lock()
+	defer handler.conn.wio.Unlock()
+	w, err := handler.conn.frameWriterFactory.NewFrameWriter(PongFrame)
+	if err != nil {
+		return 0, err
+	}
+	n, err = w.Write(msg)
+	w.Close()
+	return n, err
+}
+
+// newHybiConn creates a new WebSocket connection speaking hybi draft protocol.
+func newHybiConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
+	if buf == nil {
+		br := bufio.NewReader(rwc)
+		bw := bufio.NewWriter(rwc)
+		buf = bufio.NewReadWriter(br, bw)
+	}
+	ws := &Conn{config: config, request: request, buf: buf, rwc: rwc,
+		frameReaderFactory: hybiFrameReaderFactory{buf.Reader},
+		frameWriterFactory: hybiFrameWriterFactory{
+			buf.Writer, request == nil},
+		PayloadType:        TextFrame,
+		defaultCloseStatus: closeStatusNormal}
+	ws.frameHandler = &hybiFrameHandler{conn: ws}
+	return ws
+}
+
+// generateMaskingKey generates a masking key for a frame.
+func generateMaskingKey() (maskingKey []byte, err error) {
+	maskingKey = make([]byte, 4)
+	if _, err = io.ReadFull(rand.Reader, maskingKey); err != nil {
+		return
+	}
+	return
+}
+
+// genetateNonce geneates a nonce consisting of a randomly selected 16-byte
+// value that has been base64-encoded.
+func generateNonce() (nonce []byte) {
+	key := make([]byte, 16)
+	if _, err := io.ReadFull(rand.Reader, key); err != nil {
+		panic(err)
+	}
+	nonce = make([]byte, 24)
+	base64.StdEncoding.Encode(nonce, key)
+	return
+}
+
+// getNonceAccept computes the base64-encoded SHA-1 of the concatenation of
+// the nonce ("Sec-WebSocket-Key" value) with the websocket GUID string.
+func getNonceAccept(nonce []byte) (expected []byte, err error) {
+	h := sha1.New()
+	if _, err = h.Write(nonce); err != nil {
+		return
+	}
+	if _, err = h.Write([]byte(websocketGUID)); err != nil {
+		return
+	}
+	expected = make([]byte, 28)
+	base64.StdEncoding.Encode(expected, h.Sum(nil))
+	return
+}
+
+func isHybiVersion(version int) bool {
+	switch version {
+	case ProtocolVersionHybi08, ProtocolVersionHybi13:
+		return true
+	default:
+	}
+	return false
+}
+
+// Client handhake described in draft-ietf-hybi-thewebsocket-protocol-17
+func hybiClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) {
+	if !isHybiVersion(config.Version) {
+		panic("wrong protocol version.")
+	}
+
+	bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")
+
+	bw.WriteString("Host: " + config.Location.Host + "\r\n")
+	bw.WriteString("Upgrade: websocket\r\n")
+	bw.WriteString("Connection: Upgrade\r\n")
+	nonce := generateNonce()
+	if config.handshakeData != nil {
+		nonce = []byte(config.handshakeData["key"])
+	}
+	bw.WriteString("Sec-WebSocket-Key: " + string(nonce) + "\r\n")
+	if config.Version == ProtocolVersionHybi13 {
+		bw.WriteString("Origin: " + strings.ToLower(config.Origin.String()) + "\r\n")
+	} else if config.Version == ProtocolVersionHybi08 {
+		bw.WriteString("Sec-WebSocket-Origin: " + strings.ToLower(config.Origin.String()) + "\r\n")
+	}
+	bw.WriteString("Sec-WebSocket-Version: " + fmt.Sprintf("%d", config.Version) + "\r\n")
+	if len(config.Protocol) > 0 {
+		bw.WriteString("Sec-WebSocket-Protocol: " + strings.Join(config.Protocol, ", ") + "\r\n")
+	}
+	// TODO(ukai): send extensions.
+	// TODO(ukai): send cookie if any.
+
+	bw.WriteString("\r\n")
+	if err = bw.Flush(); err != nil {
+		return err
+	}
+
+	resp, err := http.ReadResponse(br, &http.Request{Method: "GET"})
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != 101 {
+		return ErrBadStatus
+	}
+	if strings.ToLower(resp.Header.Get("Upgrade")) != "websocket" ||
+		strings.ToLower(resp.Header.Get("Connection")) != "upgrade" {
+		return ErrBadUpgrade
+	}
+	expectedAccept, err := getNonceAccept(nonce)
+	if err != nil {
+		return err
+	}
+	if resp.Header.Get("Sec-WebSocket-Accept") != string(expectedAccept) {
+		return ErrChallengeResponse
+	}
+	if resp.Header.Get("Sec-WebSocket-Extensions") != "" {
+		return ErrUnsupportedExtensions
+	}
+	offeredProtocol := resp.Header.Get("Sec-WebSocket-Protocol")
+	if offeredProtocol != "" {
+		protocolMatched := false
+		for i := 0; i < len(config.Protocol); i++ {
+			if config.Protocol[i] == offeredProtocol {
+				protocolMatched = true
+				break
+			}
+		}
+		if !protocolMatched {
+			return ErrBadWebSocketProtocol
+		}
+		config.Protocol = []string{offeredProtocol}
+	}
+
+	return nil
+}
+
+// newHybiClientConn creates a client WebSocket connection after handshake.
+func newHybiClientConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser) *Conn {
+	return newHybiConn(config, buf, rwc, nil)
+}
+
+// A HybiServerHandshaker performs a server handshake using hybi draft protocol.
+type hybiServerHandshaker struct {
+	*Config
+	accept []byte
+}
+
+func (c *hybiServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) {
+	c.Version = ProtocolVersionHybi13
+	if req.Method != "GET" {
+		return http.StatusMethodNotAllowed, ErrBadRequestMethod
+	}
+	// HTTP version can be safely ignored.
+
+	if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" ||
+		!strings.Contains(strings.ToLower(req.Header.Get("Connection")), "upgrade") {
+		return http.StatusBadRequest, ErrNotWebSocket
+	}
+
+	key := req.Header.Get("Sec-Websocket-Key")
+	if key == "" {
+		return http.StatusBadRequest, ErrChallengeResponse
+	}
+	version := req.Header.Get("Sec-Websocket-Version")
+	var origin string
+	switch version {
+	case "13":
+		c.Version = ProtocolVersionHybi13
+		origin = req.Header.Get("Origin")
+	case "8":
+		c.Version = ProtocolVersionHybi08
+		origin = req.Header.Get("Sec-Websocket-Origin")
+	default:
+		return http.StatusBadRequest, ErrBadWebSocketVersion
+	}
+	c.Origin, err = url.ParseRequestURI(origin)
+	if err != nil {
+		return http.StatusForbidden, err
+	}
+	var scheme string
+	if req.TLS != nil {
+		scheme = "wss"
+	} else {
+		scheme = "ws"
+	}
+	c.Location, err = url.ParseRequestURI(scheme + "://" + req.Host + req.URL.RequestURI())
+	if err != nil {
+		return http.StatusBadRequest, err
+	}
+	protocol := strings.TrimSpace(req.Header.Get("Sec-Websocket-Protocol"))
+	protocols := strings.Split(protocol, ",")
+	for i := 0; i < len(protocols); i++ {
+		c.Protocol = append(c.Protocol, strings.TrimSpace(protocols[i]))
+	}
+	c.accept, err = getNonceAccept([]byte(key))
+	if err != nil {
+		return http.StatusInternalServerError, err
+	}
+	return http.StatusSwitchingProtocols, nil
+}
+
+func (c *hybiServerHandshaker) AcceptHandshake(buf *bufio.Writer) (err error) {
+	if len(c.Protocol) > 0 {
+		if len(c.Protocol) != 1 {
+			return ErrBadWebSocketProtocol
+		}
+	}
+	buf.WriteString("HTTP/1.1 101 Switching Protocols\r\n")
+	buf.WriteString("Upgrade: websocket\r\n")
+	buf.WriteString("Connection: Upgrade\r\n")
+	buf.WriteString("Sec-WebSocket-Accept: " + string(c.accept) + "\r\n")
+	if len(c.Protocol) > 0 {
+		buf.WriteString("Sec-WebSocket-Protocol: " + c.Protocol[0] + "\r\n")
+	}
+	// TODO(ukai): support extensions
+	buf.WriteString("\r\n")
+	return buf.Flush()
+}
+
+func (c *hybiServerHandshaker) NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
+	return newHybiServerConn(c.Config, buf, rwc, request)
+}
+
+// newHybiServerConn returns a new WebSocket connection speaking hybi draft protocol.
+func newHybiServerConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
+	return newHybiConn(config, buf, rwc, request)
+}

+ 584 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/websocket/hybi_test.go

@@ -0,0 +1,584 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package websocket
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"strings"
+	"testing"
+)
+
+// Test the getNonceAccept function with values in
+// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
+func TestSecWebSocketAccept(t *testing.T) {
+	nonce := []byte("dGhlIHNhbXBsZSBub25jZQ==")
+	expected := []byte("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=")
+	accept, err := getNonceAccept(nonce)
+	if err != nil {
+		t.Errorf("getNonceAccept: returned error %v", err)
+		return
+	}
+	if !bytes.Equal(expected, accept) {
+		t.Errorf("getNonceAccept: expected %q got %q", expected, accept)
+	}
+}
+
+func TestHybiClientHandshake(t *testing.T) {
+	b := bytes.NewBuffer([]byte{})
+	bw := bufio.NewWriter(b)
+	br := bufio.NewReader(strings.NewReader(`HTTP/1.1 101 Switching Protocols
+Upgrade: websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
+Sec-WebSocket-Protocol: chat
+
+`))
+	var err error
+	config := new(Config)
+	config.Location, err = url.ParseRequestURI("ws://server.example.com/chat")
+	if err != nil {
+		t.Fatal("location url", err)
+	}
+	config.Origin, err = url.ParseRequestURI("http://example.com")
+	if err != nil {
+		t.Fatal("origin url", err)
+	}
+	config.Protocol = append(config.Protocol, "chat")
+	config.Protocol = append(config.Protocol, "superchat")
+	config.Version = ProtocolVersionHybi13
+
+	config.handshakeData = map[string]string{
+		"key": "dGhlIHNhbXBsZSBub25jZQ==",
+	}
+	err = hybiClientHandshake(config, br, bw)
+	if err != nil {
+		t.Errorf("handshake failed: %v", err)
+	}
+	req, err := http.ReadRequest(bufio.NewReader(b))
+	if err != nil {
+		t.Fatalf("read request: %v", err)
+	}
+	if req.Method != "GET" {
+		t.Errorf("request method expected GET, but got %q", req.Method)
+	}
+	if req.URL.Path != "/chat" {
+		t.Errorf("request path expected /chat, but got %q", req.URL.Path)
+	}
+	if req.Proto != "HTTP/1.1" {
+		t.Errorf("request proto expected HTTP/1.1, but got %q", req.Proto)
+	}
+	if req.Host != "server.example.com" {
+		t.Errorf("request Host expected server.example.com, but got %v", req.Host)
+	}
+	var expectedHeader = map[string]string{
+		"Connection":             "Upgrade",
+		"Upgrade":                "websocket",
+		"Sec-Websocket-Key":      config.handshakeData["key"],
+		"Origin":                 config.Origin.String(),
+		"Sec-Websocket-Protocol": "chat, superchat",
+		"Sec-Websocket-Version":  fmt.Sprintf("%d", ProtocolVersionHybi13),
+	}
+	for k, v := range expectedHeader {
+		if req.Header.Get(k) != v {
+			t.Errorf(fmt.Sprintf("%s expected %q but got %q", k, v, req.Header.Get(k)))
+		}
+	}
+}
+
+func TestHybiClientHandshakeHybi08(t *testing.T) {
+	b := bytes.NewBuffer([]byte{})
+	bw := bufio.NewWriter(b)
+	br := bufio.NewReader(strings.NewReader(`HTTP/1.1 101 Switching Protocols
+Upgrade: websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
+Sec-WebSocket-Protocol: chat
+
+`))
+	var err error
+	config := new(Config)
+	config.Location, err = url.ParseRequestURI("ws://server.example.com/chat")
+	if err != nil {
+		t.Fatal("location url", err)
+	}
+	config.Origin, err = url.ParseRequestURI("http://example.com")
+	if err != nil {
+		t.Fatal("origin url", err)
+	}
+	config.Protocol = append(config.Protocol, "chat")
+	config.Protocol = append(config.Protocol, "superchat")
+	config.Version = ProtocolVersionHybi08
+
+	config.handshakeData = map[string]string{
+		"key": "dGhlIHNhbXBsZSBub25jZQ==",
+	}
+	err = hybiClientHandshake(config, br, bw)
+	if err != nil {
+		t.Errorf("handshake failed: %v", err)
+	}
+	req, err := http.ReadRequest(bufio.NewReader(b))
+	if err != nil {
+		t.Fatalf("read request: %v", err)
+	}
+	if req.Method != "GET" {
+		t.Errorf("request method expected GET, but got %q", req.Method)
+	}
+	if req.URL.Path != "/chat" {
+		t.Errorf("request path expected /demo, but got %q", req.URL.Path)
+	}
+	if req.Proto != "HTTP/1.1" {
+		t.Errorf("request proto expected HTTP/1.1, but got %q", req.Proto)
+	}
+	if req.Host != "server.example.com" {
+		t.Errorf("request Host expected example.com, but got %v", req.Host)
+	}
+	var expectedHeader = map[string]string{
+		"Connection":             "Upgrade",
+		"Upgrade":                "websocket",
+		"Sec-Websocket-Key":      config.handshakeData["key"],
+		"Sec-Websocket-Origin":   config.Origin.String(),
+		"Sec-Websocket-Protocol": "chat, superchat",
+		"Sec-Websocket-Version":  fmt.Sprintf("%d", ProtocolVersionHybi08),
+	}
+	for k, v := range expectedHeader {
+		if req.Header.Get(k) != v {
+			t.Errorf(fmt.Sprintf("%s expected %q but got %q", k, v, req.Header.Get(k)))
+		}
+	}
+}
+
+func TestHybiServerHandshake(t *testing.T) {
+	config := new(Config)
+	handshaker := &hybiServerHandshaker{Config: config}
+	br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1
+Host: server.example.com
+Upgrade: websocket
+Connection: Upgrade
+Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
+Origin: http://example.com
+Sec-WebSocket-Protocol: chat, superchat
+Sec-WebSocket-Version: 13
+
+`))
+	req, err := http.ReadRequest(br)
+	if err != nil {
+		t.Fatal("request", err)
+	}
+	code, err := handshaker.ReadHandshake(br, req)
+	if err != nil {
+		t.Errorf("handshake failed: %v", err)
+	}
+	if code != http.StatusSwitchingProtocols {
+		t.Errorf("status expected %q but got %q", http.StatusSwitchingProtocols, code)
+	}
+	b := bytes.NewBuffer([]byte{})
+	bw := bufio.NewWriter(b)
+
+	config.Protocol = []string{"chat"}
+
+	err = handshaker.AcceptHandshake(bw)
+	if err != nil {
+		t.Errorf("handshake response failed: %v", err)
+	}
+	expectedResponse := strings.Join([]string{
+		"HTTP/1.1 101 Switching Protocols",
+		"Upgrade: websocket",
+		"Connection: Upgrade",
+		"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",
+		"Sec-WebSocket-Protocol: chat",
+		"", ""}, "\r\n")
+
+	if b.String() != expectedResponse {
+		t.Errorf("handshake expected %q but got %q", expectedResponse, b.String())
+	}
+}
+
+func TestHybiServerHandshakeHybi08(t *testing.T) {
+	config := new(Config)
+	handshaker := &hybiServerHandshaker{Config: config}
+	br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1
+Host: server.example.com
+Upgrade: websocket
+Connection: Upgrade
+Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
+Sec-WebSocket-Origin: http://example.com
+Sec-WebSocket-Protocol: chat, superchat
+Sec-WebSocket-Version: 8
+
+`))
+	req, err := http.ReadRequest(br)
+	if err != nil {
+		t.Fatal("request", err)
+	}
+	code, err := handshaker.ReadHandshake(br, req)
+	if err != nil {
+		t.Errorf("handshake failed: %v", err)
+	}
+	if code != http.StatusSwitchingProtocols {
+		t.Errorf("status expected %q but got %q", http.StatusSwitchingProtocols, code)
+	}
+	b := bytes.NewBuffer([]byte{})
+	bw := bufio.NewWriter(b)
+
+	config.Protocol = []string{"chat"}
+
+	err = handshaker.AcceptHandshake(bw)
+	if err != nil {
+		t.Errorf("handshake response failed: %v", err)
+	}
+	expectedResponse := strings.Join([]string{
+		"HTTP/1.1 101 Switching Protocols",
+		"Upgrade: websocket",
+		"Connection: Upgrade",
+		"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",
+		"Sec-WebSocket-Protocol: chat",
+		"", ""}, "\r\n")
+
+	if b.String() != expectedResponse {
+		t.Errorf("handshake expected %q but got %q", expectedResponse, b.String())
+	}
+}
+
+func TestHybiServerHandshakeHybiBadVersion(t *testing.T) {
+	config := new(Config)
+	handshaker := &hybiServerHandshaker{Config: config}
+	br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1
+Host: server.example.com
+Upgrade: websocket
+Connection: Upgrade
+Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
+Sec-WebSocket-Origin: http://example.com
+Sec-WebSocket-Protocol: chat, superchat
+Sec-WebSocket-Version: 9
+
+`))
+	req, err := http.ReadRequest(br)
+	if err != nil {
+		t.Fatal("request", err)
+	}
+	code, err := handshaker.ReadHandshake(br, req)
+	if err != ErrBadWebSocketVersion {
+		t.Errorf("handshake expected err %q but got %q", ErrBadWebSocketVersion, err)
+	}
+	if code != http.StatusBadRequest {
+		t.Errorf("status expected %q but got %q", http.StatusBadRequest, code)
+	}
+}
+
+func testHybiFrame(t *testing.T, testHeader, testPayload, testMaskedPayload []byte, frameHeader *hybiFrameHeader) {
+	b := bytes.NewBuffer([]byte{})
+	frameWriterFactory := &hybiFrameWriterFactory{bufio.NewWriter(b), false}
+	w, _ := frameWriterFactory.NewFrameWriter(TextFrame)
+	w.(*hybiFrameWriter).header = frameHeader
+	_, err := w.Write(testPayload)
+	w.Close()
+	if err != nil {
+		t.Errorf("Write error %q", err)
+	}
+	var expectedFrame []byte
+	expectedFrame = append(expectedFrame, testHeader...)
+	expectedFrame = append(expectedFrame, testMaskedPayload...)
+	if !bytes.Equal(expectedFrame, b.Bytes()) {
+		t.Errorf("frame expected %q got %q", expectedFrame, b.Bytes())
+	}
+	frameReaderFactory := &hybiFrameReaderFactory{bufio.NewReader(b)}
+	r, err := frameReaderFactory.NewFrameReader()
+	if err != nil {
+		t.Errorf("Read error %q", err)
+	}
+	if header := r.HeaderReader(); header == nil {
+		t.Errorf("no header")
+	} else {
+		actualHeader := make([]byte, r.Len())
+		n, err := header.Read(actualHeader)
+		if err != nil {
+			t.Errorf("Read header error %q", err)
+		} else {
+			if n < len(testHeader) {
+				t.Errorf("header too short %q got %q", testHeader, actualHeader[:n])
+			}
+			if !bytes.Equal(testHeader, actualHeader[:n]) {
+				t.Errorf("header expected %q got %q", testHeader, actualHeader[:n])
+			}
+		}
+	}
+	if trailer := r.TrailerReader(); trailer != nil {
+		t.Errorf("unexpected trailer %q", trailer)
+	}
+	frame := r.(*hybiFrameReader)
+	if frameHeader.Fin != frame.header.Fin ||
+		frameHeader.OpCode != frame.header.OpCode ||
+		len(testPayload) != int(frame.header.Length) {
+		t.Errorf("mismatch %v (%d) vs %v", frameHeader, len(testPayload), frame)
+	}
+	payload := make([]byte, len(testPayload))
+	_, err = r.Read(payload)
+	if err != nil {
+		t.Errorf("read %v", err)
+	}
+	if !bytes.Equal(testPayload, payload) {
+		t.Errorf("payload %q vs %q", testPayload, payload)
+	}
+}
+
+func TestHybiShortTextFrame(t *testing.T) {
+	frameHeader := &hybiFrameHeader{Fin: true, OpCode: TextFrame}
+	payload := []byte("hello")
+	testHybiFrame(t, []byte{0x81, 0x05}, payload, payload, frameHeader)
+
+	payload = make([]byte, 125)
+	testHybiFrame(t, []byte{0x81, 125}, payload, payload, frameHeader)
+}
+
+func TestHybiShortMaskedTextFrame(t *testing.T) {
+	frameHeader := &hybiFrameHeader{Fin: true, OpCode: TextFrame,
+		MaskingKey: []byte{0xcc, 0x55, 0x80, 0x20}}
+	payload := []byte("hello")
+	maskedPayload := []byte{0xa4, 0x30, 0xec, 0x4c, 0xa3}
+	header := []byte{0x81, 0x85}
+	header = append(header, frameHeader.MaskingKey...)
+	testHybiFrame(t, header, payload, maskedPayload, frameHeader)
+}
+
+func TestHybiShortBinaryFrame(t *testing.T) {
+	frameHeader := &hybiFrameHeader{Fin: true, OpCode: BinaryFrame}
+	payload := []byte("hello")
+	testHybiFrame(t, []byte{0x82, 0x05}, payload, payload, frameHeader)
+
+	payload = make([]byte, 125)
+	testHybiFrame(t, []byte{0x82, 125}, payload, payload, frameHeader)
+}
+
+func TestHybiControlFrame(t *testing.T) {
+	frameHeader := &hybiFrameHeader{Fin: true, OpCode: PingFrame}
+	payload := []byte("hello")
+	testHybiFrame(t, []byte{0x89, 0x05}, payload, payload, frameHeader)
+
+	frameHeader = &hybiFrameHeader{Fin: true, OpCode: PongFrame}
+	testHybiFrame(t, []byte{0x8A, 0x05}, payload, payload, frameHeader)
+
+	frameHeader = &hybiFrameHeader{Fin: true, OpCode: CloseFrame}
+	payload = []byte{0x03, 0xe8} // 1000
+	testHybiFrame(t, []byte{0x88, 0x02}, payload, payload, frameHeader)
+}
+
+func TestHybiLongFrame(t *testing.T) {
+	frameHeader := &hybiFrameHeader{Fin: true, OpCode: TextFrame}
+	payload := make([]byte, 126)
+	testHybiFrame(t, []byte{0x81, 126, 0x00, 126}, payload, payload, frameHeader)
+
+	payload = make([]byte, 65535)
+	testHybiFrame(t, []byte{0x81, 126, 0xff, 0xff}, payload, payload, frameHeader)
+
+	payload = make([]byte, 65536)
+	testHybiFrame(t, []byte{0x81, 127, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00}, payload, payload, frameHeader)
+}
+
+func TestHybiClientRead(t *testing.T) {
+	wireData := []byte{0x81, 0x05, 'h', 'e', 'l', 'l', 'o',
+		0x89, 0x05, 'h', 'e', 'l', 'l', 'o', // ping
+		0x81, 0x05, 'w', 'o', 'r', 'l', 'd'}
+	br := bufio.NewReader(bytes.NewBuffer(wireData))
+	bw := bufio.NewWriter(bytes.NewBuffer([]byte{}))
+	conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, nil)
+
+	msg := make([]byte, 512)
+	n, err := conn.Read(msg)
+	if err != nil {
+		t.Errorf("read 1st frame, error %q", err)
+	}
+	if n != 5 {
+		t.Errorf("read 1st frame, expect 5, got %d", n)
+	}
+	if !bytes.Equal(wireData[2:7], msg[:n]) {
+		t.Errorf("read 1st frame %v, got %v", wireData[2:7], msg[:n])
+	}
+	n, err = conn.Read(msg)
+	if err != nil {
+		t.Errorf("read 2nd frame, error %q", err)
+	}
+	if n != 5 {
+		t.Errorf("read 2nd frame, expect 5, got %d", n)
+	}
+	if !bytes.Equal(wireData[16:21], msg[:n]) {
+		t.Errorf("read 2nd frame %v, got %v", wireData[16:21], msg[:n])
+	}
+	n, err = conn.Read(msg)
+	if err == nil {
+		t.Errorf("read not EOF")
+	}
+	if n != 0 {
+		t.Errorf("expect read 0, got %d", n)
+	}
+}
+
+func TestHybiShortRead(t *testing.T) {
+	wireData := []byte{0x81, 0x05, 'h', 'e', 'l', 'l', 'o',
+		0x89, 0x05, 'h', 'e', 'l', 'l', 'o', // ping
+		0x81, 0x05, 'w', 'o', 'r', 'l', 'd'}
+	br := bufio.NewReader(bytes.NewBuffer(wireData))
+	bw := bufio.NewWriter(bytes.NewBuffer([]byte{}))
+	conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, nil)
+
+	step := 0
+	pos := 0
+	expectedPos := []int{2, 5, 16, 19}
+	expectedLen := []int{3, 2, 3, 2}
+	for {
+		msg := make([]byte, 3)
+		n, err := conn.Read(msg)
+		if step >= len(expectedPos) {
+			if err == nil {
+				t.Errorf("read not EOF")
+			}
+			if n != 0 {
+				t.Errorf("expect read 0, got %d", n)
+			}
+			return
+		}
+		pos = expectedPos[step]
+		endPos := pos + expectedLen[step]
+		if err != nil {
+			t.Errorf("read from %d, got error %q", pos, err)
+			return
+		}
+		if n != endPos-pos {
+			t.Errorf("read from %d, expect %d, got %d", pos, endPos-pos, n)
+		}
+		if !bytes.Equal(wireData[pos:endPos], msg[:n]) {
+			t.Errorf("read from %d, frame %v, got %v", pos, wireData[pos:endPos], msg[:n])
+		}
+		step++
+	}
+}
+
+func TestHybiServerRead(t *testing.T) {
+	wireData := []byte{0x81, 0x85, 0xcc, 0x55, 0x80, 0x20,
+		0xa4, 0x30, 0xec, 0x4c, 0xa3, // hello
+		0x89, 0x85, 0xcc, 0x55, 0x80, 0x20,
+		0xa4, 0x30, 0xec, 0x4c, 0xa3, // ping: hello
+		0x81, 0x85, 0xed, 0x83, 0xb4, 0x24,
+		0x9a, 0xec, 0xc6, 0x48, 0x89, // world
+	}
+	br := bufio.NewReader(bytes.NewBuffer(wireData))
+	bw := bufio.NewWriter(bytes.NewBuffer([]byte{}))
+	conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, new(http.Request))
+
+	expected := [][]byte{[]byte("hello"), []byte("world")}
+
+	msg := make([]byte, 512)
+	n, err := conn.Read(msg)
+	if err != nil {
+		t.Errorf("read 1st frame, error %q", err)
+	}
+	if n != 5 {
+		t.Errorf("read 1st frame, expect 5, got %d", n)
+	}
+	if !bytes.Equal(expected[0], msg[:n]) {
+		t.Errorf("read 1st frame %q, got %q", expected[0], msg[:n])
+	}
+
+	n, err = conn.Read(msg)
+	if err != nil {
+		t.Errorf("read 2nd frame, error %q", err)
+	}
+	if n != 5 {
+		t.Errorf("read 2nd frame, expect 5, got %d", n)
+	}
+	if !bytes.Equal(expected[1], msg[:n]) {
+		t.Errorf("read 2nd frame %q, got %q", expected[1], msg[:n])
+	}
+
+	n, err = conn.Read(msg)
+	if err == nil {
+		t.Errorf("read not EOF")
+	}
+	if n != 0 {
+		t.Errorf("expect read 0, got %d", n)
+	}
+}
+
+func TestHybiServerReadWithoutMasking(t *testing.T) {
+	wireData := []byte{0x81, 0x05, 'h', 'e', 'l', 'l', 'o'}
+	br := bufio.NewReader(bytes.NewBuffer(wireData))
+	bw := bufio.NewWriter(bytes.NewBuffer([]byte{}))
+	conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, new(http.Request))
+	// server MUST close the connection upon receiving a non-masked frame.
+	msg := make([]byte, 512)
+	_, err := conn.Read(msg)
+	if err != io.EOF {
+		t.Errorf("read 1st frame, expect %q, but got %q", io.EOF, err)
+	}
+}
+
+func TestHybiClientReadWithMasking(t *testing.T) {
+	wireData := []byte{0x81, 0x85, 0xcc, 0x55, 0x80, 0x20,
+		0xa4, 0x30, 0xec, 0x4c, 0xa3, // hello
+	}
+	br := bufio.NewReader(bytes.NewBuffer(wireData))
+	bw := bufio.NewWriter(bytes.NewBuffer([]byte{}))
+	conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, nil)
+
+	// client MUST close the connection upon receiving a masked frame.
+	msg := make([]byte, 512)
+	_, err := conn.Read(msg)
+	if err != io.EOF {
+		t.Errorf("read 1st frame, expect %q, but got %q", io.EOF, err)
+	}
+}
+
+// Test the hybiServerHandshaker supports firefox implementation and
+// checks Connection request header include (but it's not necessary 
+// equal to) "upgrade"   
+func TestHybiServerFirefoxHandshake(t *testing.T) {
+	config := new(Config)
+	handshaker := &hybiServerHandshaker{Config: config}
+	br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1
+Host: server.example.com
+Upgrade: websocket
+Connection: keep-alive, upgrade
+Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
+Origin: http://example.com
+Sec-WebSocket-Protocol: chat, superchat
+Sec-WebSocket-Version: 13
+
+`))
+	req, err := http.ReadRequest(br)
+	if err != nil {
+		t.Fatal("request", err)
+	}
+	code, err := handshaker.ReadHandshake(br, req)
+	if err != nil {
+		t.Errorf("handshake failed: %v", err)
+	}
+	if code != http.StatusSwitchingProtocols {
+		t.Errorf("status expected %q but got %q", http.StatusSwitchingProtocols, code)
+	}
+	b := bytes.NewBuffer([]byte{})
+	bw := bufio.NewWriter(b)
+
+	config.Protocol = []string{"chat"}
+
+	err = handshaker.AcceptHandshake(bw)
+	if err != nil {
+		t.Errorf("handshake response failed: %v", err)
+	}
+	expectedResponse := strings.Join([]string{
+		"HTTP/1.1 101 Switching Protocols",
+		"Upgrade: websocket",
+		"Connection: Upgrade",
+		"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",
+		"Sec-WebSocket-Protocol: chat",
+		"", ""}, "\r\n")
+
+	if b.String() != expectedResponse {
+		t.Errorf("handshake expected %q but got %q", expectedResponse, b.String())
+	}
+}

+ 102 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/websocket/server.go

@@ -0,0 +1,102 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package websocket
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"net/http"
+)
+
+func newServerConn(rwc io.ReadWriteCloser, buf *bufio.ReadWriter, req *http.Request) (conn *Conn, err error) {
+	config := new(Config)
+	var hs serverHandshaker = &hybiServerHandshaker{Config: config}
+	code, err := hs.ReadHandshake(buf.Reader, req)
+	if err == ErrBadWebSocketVersion {
+		fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
+		fmt.Fprintf(buf, "Sec-WebSocket-Version: %s\r\n", SupportedProtocolVersion)
+		buf.WriteString("\r\n")
+		buf.WriteString(err.Error())
+		buf.Flush()
+		return
+	}
+	if err != nil {
+		hs = &hixie76ServerHandshaker{Config: config}
+		code, err = hs.ReadHandshake(buf.Reader, req)
+	}
+	if err != nil {
+		hs = &hixie75ServerHandshaker{Config: config}
+		code, err = hs.ReadHandshake(buf.Reader, req)
+	}
+	if err != nil {
+		fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
+		buf.WriteString("\r\n")
+		buf.WriteString(err.Error())
+		buf.Flush()
+		return
+	}
+	config.Protocol = nil
+
+	err = hs.AcceptHandshake(buf.Writer)
+	if err != nil {
+		code = http.StatusBadRequest
+		fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
+		buf.WriteString("\r\n")
+		buf.Flush()
+		return
+	}
+	conn = hs.NewServerConn(buf, rwc, req)
+	return
+}
+
+/*
+Handler is an interface to a WebSocket.
+
+A trivial example server:
+
+	package main
+
+	import (
+		"io"
+		"net/http"
+		"websocket"
+	)
+
+	// Echo the data received on the WebSocket.
+	func EchoServer(ws *websocket.Conn) {
+		io.Copy(ws, ws);
+	}
+
+	func main() {
+		http.Handle("/echo", websocket.Handler(EchoServer));
+		err := http.ListenAndServe(":12345", nil);
+		if err != nil {
+			panic("ListenAndServe: " + err.Error())
+		}
+	}
+*/
+type Handler func(*Conn)
+
+// ServeHTTP implements the http.Handler interface for a Web Socket
+func (h Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+	rwc, buf, err := w.(http.Hijacker).Hijack()
+	if err != nil {
+		panic("Hijack failed: " + err.Error())
+		return
+	}
+	// The server should abort the WebSocket connection if it finds
+	// the client did not send a handshake that matches with protocol
+	// specification.
+	defer rwc.Close()
+	conn, err := newServerConn(rwc, buf, req)
+	if err != nil {
+		return
+	}
+	if conn == nil {
+		panic("unepxected nil conn")
+	}
+	h(conn)
+}

+ 412 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/websocket/websocket.go

@@ -0,0 +1,412 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package websocket implements a client and server for the WebSocket protocol.
+// The protocol is defined at http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol
+package websocket
+
+import (
+	"bufio"
+	"crypto/tls"
+	"encoding/json"
+	"errors"
+	"io"
+	"io/ioutil"
+	"net"
+	"net/http"
+	"net/url"
+	"sync"
+	"time"
+)
+
+const (
+	ProtocolVersionHixie75   = -75
+	ProtocolVersionHixie76   = -76
+	ProtocolVersionHybi00    = 0
+	ProtocolVersionHybi08    = 8
+	ProtocolVersionHybi13    = 13
+	ProtocolVersionHybi      = ProtocolVersionHybi13
+	SupportedProtocolVersion = "13, 8"
+
+	ContinuationFrame = 0
+	TextFrame         = 1
+	BinaryFrame       = 2
+	CloseFrame        = 8
+	PingFrame         = 9
+	PongFrame         = 10
+	UnknownFrame      = 255
+)
+
+// WebSocket protocol errors.
+type ProtocolError struct {
+	ErrorString string
+}
+
+func (err *ProtocolError) Error() string { return err.ErrorString }
+
+var (
+	ErrBadProtocolVersion   = &ProtocolError{"bad protocol version"}
+	ErrBadScheme            = &ProtocolError{"bad scheme"}
+	ErrBadStatus            = &ProtocolError{"bad status"}
+	ErrBadUpgrade           = &ProtocolError{"missing or bad upgrade"}
+	ErrBadWebSocketOrigin   = &ProtocolError{"missing or bad WebSocket-Origin"}
+	ErrBadWebSocketLocation = &ProtocolError{"missing or bad WebSocket-Location"}
+	ErrBadWebSocketProtocol = &ProtocolError{"missing or bad WebSocket-Protocol"}
+	ErrBadWebSocketVersion  = &ProtocolError{"missing or bad WebSocket Version"}
+	ErrChallengeResponse    = &ProtocolError{"mismatch challenge/response"}
+	ErrBadFrame             = &ProtocolError{"bad frame"}
+	ErrBadFrameBoundary     = &ProtocolError{"not on frame boundary"}
+	ErrNotWebSocket         = &ProtocolError{"not websocket protocol"}
+	ErrBadRequestMethod     = &ProtocolError{"bad method"}
+	ErrNotSupported         = &ProtocolError{"not supported"}
+)
+
+// Addr is an implementation of net.Addr for WebSocket.
+type Addr struct {
+	*url.URL
+}
+
+// Network returns the network type for a WebSocket, "websocket".
+func (addr *Addr) Network() string { return "websocket" }
+
+// Config is a WebSocket configuration
+type Config struct {
+	// A WebSocket server address.
+	Location *url.URL
+
+	// A Websocket client origin.
+	Origin *url.URL
+
+	// WebSocket subprotocols.
+	Protocol []string
+
+	// WebSocket protocol version.
+	Version int
+
+	// TLS config for secure WebSocket (wss).
+	TlsConfig *tls.Config
+
+	handshakeData map[string]string
+}
+
+// serverHandshaker is an interface to handle WebSocket server side handshake.
+type serverHandshaker interface {
+	// ReadHandshake reads handshake request message from client.
+	// Returns http response code and error if any.
+	ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error)
+
+	// AcceptHandshake accepts the client handshake request and sends
+	// handshake response back to client.
+	AcceptHandshake(buf *bufio.Writer) (err error)
+
+	// NewServerConn creates a new WebSocket connection.
+	NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) (conn *Conn)
+}
+
+// frameReader is an interface to read a WebSocket frame.
+type frameReader interface {
+	// Reader is to read payload of the frame.
+	io.Reader
+
+	// PayloadType returns payload type.
+	PayloadType() byte
+
+	// HeaderReader returns a reader to read header of the frame.
+	HeaderReader() io.Reader
+
+	// TrailerReader returns a reader to read trailer of the frame.
+	// If it returns nil, there is no trailer in the frame.
+	TrailerReader() io.Reader
+
+	// Len returns total length of the frame, including header and trailer.
+	Len() int
+}
+
+// frameReaderFactory is an interface to creates new frame reader.
+type frameReaderFactory interface {
+	NewFrameReader() (r frameReader, err error)
+}
+
+// frameWriter is an interface to write a WebSocket frame.
+type frameWriter interface {
+	// Writer is to write playload of the frame.
+	io.WriteCloser
+}
+
+// frameWriterFactory is an interface to create new frame writer.
+type frameWriterFactory interface {
+	NewFrameWriter(payloadType byte) (w frameWriter, err error)
+}
+
+type frameHandler interface {
+	HandleFrame(frame frameReader) (r frameReader, err error)
+	WriteClose(status int) (err error)
+}
+
+// Conn represents a WebSocket connection.
+type Conn struct {
+	config  *Config
+	request *http.Request
+
+	buf *bufio.ReadWriter
+	rwc io.ReadWriteCloser
+
+	rio sync.Mutex
+	frameReaderFactory
+	frameReader
+
+	wio sync.Mutex
+	frameWriterFactory
+
+	frameHandler
+	PayloadType        byte
+	defaultCloseStatus int
+}
+
+// Read implements the io.Reader interface:
+// it reads data of a frame from the WebSocket connection.
+// if msg is not large enough for the frame data, it fills the msg and next Read
+// will read the rest of the frame data.
+// it reads Text frame or Binary frame.
+func (ws *Conn) Read(msg []byte) (n int, err error) {
+	ws.rio.Lock()
+	defer ws.rio.Unlock()
+again:
+	if ws.frameReader == nil {
+		frame, err := ws.frameReaderFactory.NewFrameReader()
+		if err != nil {
+			return 0, err
+		}
+		ws.frameReader, err = ws.frameHandler.HandleFrame(frame)
+		if err != nil {
+			return 0, err
+		}
+		if ws.frameReader == nil {
+			goto again
+		}
+	}
+	n, err = ws.frameReader.Read(msg)
+	if err == io.EOF {
+		if trailer := ws.frameReader.TrailerReader(); trailer != nil {
+			io.Copy(ioutil.Discard, trailer)
+		}
+		ws.frameReader = nil
+		goto again
+	}
+	return n, err
+}
+
+// Write implements the io.Writer interface:
+// it writes data as a frame to the WebSocket connection.
+func (ws *Conn) Write(msg []byte) (n int, err error) {
+	ws.wio.Lock()
+	defer ws.wio.Unlock()
+	w, err := ws.frameWriterFactory.NewFrameWriter(ws.PayloadType)
+	if err != nil {
+		return 0, err
+	}
+	n, err = w.Write(msg)
+	w.Close()
+	if err != nil {
+		return n, err
+	}
+	return n, err
+}
+
+// Close implements the io.Closer interface.
+func (ws *Conn) Close() error {
+	err := ws.frameHandler.WriteClose(ws.defaultCloseStatus)
+	if err != nil {
+		return err
+	}
+	return ws.rwc.Close()
+}
+
+func (ws *Conn) IsClientConn() bool { return ws.request == nil }
+func (ws *Conn) IsServerConn() bool { return ws.request != nil }
+
+// LocalAddr returns the WebSocket Origin for the connection for client, or
+// the WebSocket location for server.
+func (ws *Conn) LocalAddr() net.Addr {
+	if ws.IsClientConn() {
+		return &Addr{ws.config.Origin}
+	}
+	return &Addr{ws.config.Location}
+}
+
+// RemoteAddr returns the WebSocket location for the connection for client, or
+// the Websocket Origin for server.
+func (ws *Conn) RemoteAddr() net.Addr {
+	if ws.IsClientConn() {
+		return &Addr{ws.config.Location}
+	}
+	return &Addr{ws.config.Origin}
+}
+
+var errSetDeadline = errors.New("websocket: cannot set deadline: not using a net.Conn")
+
+// SetDeadline sets the connection's network read & write deadlines.
+func (ws *Conn) SetDeadline(t time.Time) error {
+	if conn, ok := ws.rwc.(net.Conn); ok {
+		return conn.SetDeadline(t)
+	}
+	return errSetDeadline
+}
+
+// SetReadDeadline sets the connection's network read deadline.
+func (ws *Conn) SetReadDeadline(t time.Time) error {
+	if conn, ok := ws.rwc.(net.Conn); ok {
+		return conn.SetReadDeadline(t)
+	}
+	return errSetDeadline
+}
+
+// SetWriteDeadline sets the connection's network write deadline.
+func (ws *Conn) SetWriteDeadline(t time.Time) error {
+	if conn, ok := ws.rwc.(net.Conn); ok {
+		return conn.SetWriteDeadline(t)
+	}
+	return errSetDeadline
+}
+
+// Config returns the WebSocket config.
+func (ws *Conn) Config() *Config { return ws.config }
+
+// Request returns the http request upgraded to the WebSocket.
+// It is nil for client side.
+func (ws *Conn) Request() *http.Request { return ws.request }
+
+// Codec represents a symmetric pair of functions that implement a codec.
+type Codec struct {
+	Marshal   func(v interface{}) (data []byte, payloadType byte, err error)
+	Unmarshal func(data []byte, payloadType byte, v interface{}) (err error)
+}
+
+// Send sends v marshaled by cd.Marshal as single frame to ws.
+func (cd Codec) Send(ws *Conn, v interface{}) (err error) {
+	if err != nil {
+		return err
+	}
+	data, payloadType, err := cd.Marshal(v)
+	if err != nil {
+		return err
+	}
+	ws.wio.Lock()
+	defer ws.wio.Unlock()
+	w, err := ws.frameWriterFactory.NewFrameWriter(payloadType)
+	_, err = w.Write(data)
+	w.Close()
+	return err
+}
+
+// Receive receives single frame from ws, unmarshaled by cd.Unmarshal and stores in v.
+func (cd Codec) Receive(ws *Conn, v interface{}) (err error) {
+	ws.rio.Lock()
+	defer ws.rio.Unlock()
+	if ws.frameReader != nil {
+		_, err = io.Copy(ioutil.Discard, ws.frameReader)
+		if err != nil {
+			return err
+		}
+		ws.frameReader = nil
+	}
+again:
+	frame, err := ws.frameReaderFactory.NewFrameReader()
+	if err != nil {
+		return err
+	}
+	frame, err = ws.frameHandler.HandleFrame(frame)
+	if err != nil {
+		return err
+	}
+	if frame == nil {
+		goto again
+	}
+	payloadType := frame.PayloadType()
+	data, err := ioutil.ReadAll(frame)
+	if err != nil {
+		return err
+	}
+	return cd.Unmarshal(data, payloadType, v)
+}
+
+func marshal(v interface{}) (msg []byte, payloadType byte, err error) {
+	switch data := v.(type) {
+	case string:
+		return []byte(data), TextFrame, nil
+	case []byte:
+		return data, BinaryFrame, nil
+	}
+	return nil, UnknownFrame, ErrNotSupported
+}
+
+func unmarshal(msg []byte, payloadType byte, v interface{}) (err error) {
+	switch data := v.(type) {
+	case *string:
+		*data = string(msg)
+		return nil
+	case *[]byte:
+		*data = msg
+		return nil
+	}
+	return ErrNotSupported
+}
+
+/*
+Message is a codec to send/receive text/binary data in a frame on WebSocket connection.
+To send/receive text frame, use string type.
+To send/receive binary frame, use []byte type.
+
+Trivial usage:
+
+	import "websocket"
+
+	// receive text frame
+	var message string
+	websocket.Message.Receive(ws, &message)
+
+	// send text frame
+	message = "hello"
+	websocket.Message.Send(ws, message)
+
+	// receive binary frame
+	var data []byte
+	websocket.Message.Receive(ws, &data)
+
+	// send binary frame
+	data = []byte{0, 1, 2}
+	websocket.Message.Send(ws, data)
+
+*/
+var Message = Codec{marshal, unmarshal}
+
+func jsonMarshal(v interface{}) (msg []byte, payloadType byte, err error) {
+	msg, err = json.Marshal(v)
+	return msg, TextFrame, err
+}
+
+func jsonUnmarshal(msg []byte, payloadType byte, v interface{}) (err error) {
+	return json.Unmarshal(msg, v)
+}
+
+/*
+JSON is a codec to send/receive JSON data in a frame from a WebSocket connection.
+
+Trival usage:
+
+	import "websocket"
+
+	type T struct {
+		Msg string
+		Count int
+	}
+
+	// receive JSON type T
+	var data T
+	websocket.JSON.Receive(ws, &data)
+
+	// send JSON type T
+	websocket.JSON.Send(ws, data)
+*/
+var JSON = Codec{jsonMarshal, jsonUnmarshal}

+ 274 - 0
eBook/examples/chapter_15/code.google.com/p/go.net/websocket/websocket_test.go

@@ -0,0 +1,274 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package websocket
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"log"
+	"net"
+	"net/http"
+	"net/http/httptest"
+	"net/url"
+	"strings"
+	"sync"
+	"testing"
+)
+
+var serverAddr string
+var once sync.Once
+
+func echoServer(ws *Conn) { io.Copy(ws, ws) }
+
+type Count struct {
+	S string
+	N int
+}
+
+func countServer(ws *Conn) {
+	for {
+		var count Count
+		err := JSON.Receive(ws, &count)
+		if err != nil {
+			return
+		}
+		count.N++
+		count.S = strings.Repeat(count.S, count.N)
+		err = JSON.Send(ws, count)
+		if err != nil {
+			return
+		}
+	}
+}
+
+func startServer() {
+	http.Handle("/echo", Handler(echoServer))
+	http.Handle("/count", Handler(countServer))
+	server := httptest.NewServer(nil)
+	serverAddr = server.Listener.Addr().String()
+	log.Print("Test WebSocket server listening on ", serverAddr)
+}
+
+func newConfig(t *testing.T, path string) *Config {
+	config, _ := NewConfig(fmt.Sprintf("ws://%s%s", serverAddr, path), "http://localhost")
+	return config
+}
+
+func TestEcho(t *testing.T) {
+	once.Do(startServer)
+
+	// websocket.Dial()
+	client, err := net.Dial("tcp", serverAddr)
+	if err != nil {
+		t.Fatal("dialing", err)
+	}
+	conn, err := NewClient(newConfig(t, "/echo"), client)
+	if err != nil {
+		t.Errorf("WebSocket handshake error: %v", err)
+		return
+	}
+
+	msg := []byte("hello, world\n")
+	if _, err := conn.Write(msg); err != nil {
+		t.Errorf("Write: %v", err)
+	}
+	var actual_msg = make([]byte, 512)
+	n, err := conn.Read(actual_msg)
+	if err != nil {
+		t.Errorf("Read: %v", err)
+	}
+	actual_msg = actual_msg[0:n]
+	if !bytes.Equal(msg, actual_msg) {
+		t.Errorf("Echo: expected %q got %q", msg, actual_msg)
+	}
+	conn.Close()
+}
+
+func TestAddr(t *testing.T) {
+	once.Do(startServer)
+
+	// websocket.Dial()
+	client, err := net.Dial("tcp", serverAddr)
+	if err != nil {
+		t.Fatal("dialing", err)
+	}
+	conn, err := NewClient(newConfig(t, "/echo"), client)
+	if err != nil {
+		t.Errorf("WebSocket handshake error: %v", err)
+		return
+	}
+
+	ra := conn.RemoteAddr().String()
+	if !strings.HasPrefix(ra, "ws://") || !strings.HasSuffix(ra, "/echo") {
+		t.Errorf("Bad remote addr: %v", ra)
+	}
+	la := conn.LocalAddr().String()
+	if !strings.HasPrefix(la, "http://") {
+		t.Errorf("Bad local addr: %v", la)
+	}
+	conn.Close()
+}
+
+func TestCount(t *testing.T) {
+	once.Do(startServer)
+
+	// websocket.Dial()
+	client, err := net.Dial("tcp", serverAddr)
+	if err != nil {
+		t.Fatal("dialing", err)
+	}
+	conn, err := NewClient(newConfig(t, "/count"), client)
+	if err != nil {
+		t.Errorf("WebSocket handshake error: %v", err)
+		return
+	}
+
+	var count Count
+	count.S = "hello"
+	if err := JSON.Send(conn, count); err != nil {
+		t.Errorf("Write: %v", err)
+	}
+	if err := JSON.Receive(conn, &count); err != nil {
+		t.Errorf("Read: %v", err)
+	}
+	if count.N != 1 {
+		t.Errorf("count: expected %d got %d", 1, count.N)
+	}
+	if count.S != "hello" {
+		t.Errorf("count: expected %q got %q", "hello", count.S)
+	}
+	if err := JSON.Send(conn, count); err != nil {
+		t.Errorf("Write: %v", err)
+	}
+	if err := JSON.Receive(conn, &count); err != nil {
+		t.Errorf("Read: %v", err)
+	}
+	if count.N != 2 {
+		t.Errorf("count: expected %d got %d", 2, count.N)
+	}
+	if count.S != "hellohello" {
+		t.Errorf("count: expected %q got %q", "hellohello", count.S)
+	}
+	conn.Close()
+}
+
+func TestWithQuery(t *testing.T) {
+	once.Do(startServer)
+
+	client, err := net.Dial("tcp", serverAddr)
+	if err != nil {
+		t.Fatal("dialing", err)
+	}
+
+	config := newConfig(t, "/echo")
+	config.Location, err = url.ParseRequestURI(fmt.Sprintf("ws://%s/echo?q=v", serverAddr))
+	if err != nil {
+		t.Fatal("location url", err)
+	}
+
+	ws, err := NewClient(config, client)
+	if err != nil {
+		t.Errorf("WebSocket handshake: %v", err)
+		return
+	}
+	ws.Close()
+}
+
+func TestWithProtocol(t *testing.T) {
+	once.Do(startServer)
+
+	client, err := net.Dial("tcp", serverAddr)
+	if err != nil {
+		t.Fatal("dialing", err)
+	}
+
+	config := newConfig(t, "/echo")
+	config.Protocol = append(config.Protocol, "test")
+
+	ws, err := NewClient(config, client)
+	if err != nil {
+		t.Errorf("WebSocket handshake: %v", err)
+		return
+	}
+	ws.Close()
+}
+
+func TestHTTP(t *testing.T) {
+	once.Do(startServer)
+
+	// If the client did not send a handshake that matches the protocol
+	// specification, the server MUST return an HTTP respose with an
+	// appropriate error code (such as 400 Bad Request)
+	resp, err := http.Get(fmt.Sprintf("http://%s/echo", serverAddr))
+	if err != nil {
+		t.Errorf("Get: error %#v", err)
+		return
+	}
+	if resp == nil {
+		t.Error("Get: resp is null")
+		return
+	}
+	if resp.StatusCode != http.StatusBadRequest {
+		t.Errorf("Get: expected %q got %q", http.StatusBadRequest, resp.StatusCode)
+	}
+}
+
+func TestTrailingSpaces(t *testing.T) {
+	// http://code.google.com/p/go/issues/detail?id=955
+	// The last runs of this create keys with trailing spaces that should not be
+	// generated by the client.
+	once.Do(startServer)
+	config := newConfig(t, "/echo")
+	for i := 0; i < 30; i++ {
+		// body
+		ws, err := DialConfig(config)
+		if err != nil {
+			t.Errorf("Dial #%d failed: %v", i, err)
+			break
+		}
+		ws.Close()
+	}
+}
+
+func TestSmallBuffer(t *testing.T) {
+	// http://code.google.com/p/go/issues/detail?id=1145
+	// Read should be able to handle reading a fragment of a frame.
+	once.Do(startServer)
+
+	// websocket.Dial()
+	client, err := net.Dial("tcp", serverAddr)
+	if err != nil {
+		t.Fatal("dialing", err)
+	}
+	conn, err := NewClient(newConfig(t, "/echo"), client)
+	if err != nil {
+		t.Errorf("WebSocket handshake error: %v", err)
+		return
+	}
+
+	msg := []byte("hello, world\n")
+	if _, err := conn.Write(msg); err != nil {
+		t.Errorf("Write: %v", err)
+	}
+	var small_msg = make([]byte, 8)
+	n, err := conn.Read(small_msg)
+	if err != nil {
+		t.Errorf("Read: %v", err)
+	}
+	if !bytes.Equal(msg[:len(small_msg)], small_msg) {
+		t.Errorf("Echo: expected %q got %q", msg[:len(small_msg)], small_msg)
+	}
+	var second_msg = make([]byte, len(msg))
+	n, err = conn.Read(second_msg)
+	if err != nil {
+		t.Errorf("Read: %v", err)
+	}
+	second_msg = second_msg[0:n]
+	if !bytes.Equal(msg[len(small_msg):], second_msg) {
+		t.Errorf("Echo: expected %q got %q", msg[len(small_msg):], second_msg)
+	}
+	conn.Close()
+}

+ 29 - 0
eBook/examples/chapter_15/dial.go

@@ -0,0 +1,29 @@
+// dial.go.go
+package main
+
+import (
+	"fmt"
+	"net"
+	"os"
+)
+
+func main() {
+	conn, err:= net.Dial("tcp", "192.0.32.10:80")
+	checkConnection(conn, err)
+	
+	conn, err =net.Dial("udp", "192.0.32.10:80")
+	checkConnection(conn, err)
+	
+	conn, err =net.Dial("tcp", "[2620:0:2d0:200::10]:80")
+	checkConnection(conn, err)
+}
+
+func checkConnection(conn net.Conn, err error) {
+	if err!= nil {
+		fmt.Printf("error %v connecting!")
+		os.Exit(1)
+	}
+			
+	fmt.Println("Connection is made with %v", conn)
+
+}

+ 146 - 0
eBook/examples/chapter_15/elaborated_webserver.go

@@ -0,0 +1,146 @@
+//Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+package main
+
+import (
+	"bytes"
+	"expvar"
+	"flag"
+	"fmt"
+	"net/http"
+	"io"
+	"log"
+	"os"
+	"strconv"
+)
+
+// hello world, the web server
+var helloRequests = expvar.NewInt("hello-requests")
+// flags:
+var webroot = flag.String("root", "/home/user", "web root directory")
+// simple flag server
+var booleanflag = flag.Bool("boolean", true, "another flag for testing")
+
+// Simple counter server. POSTing to it will set the value.
+type Counter struct {
+	n int
+}
+
+// a channel
+type Chan chan int
+
+func main() {
+	flag.Parse()
+	http.Handle("/", http.HandlerFunc(Logger))
+	http.Handle("/go/hello", http.HandlerFunc(HelloServer))
+	// The counter is published as a variable directly.
+	ctr := new(Counter)
+	expvar.Publish("counter", ctr)
+	http.Handle("/counter", ctr)
+	// http.Handle("/go/", http.FileServer(http.Dir("/tmp"))) // uses the OS filesystem
+	http.Handle("/go/", http.StripPrefix("/go/", http.FileServer(http.Dir(*webroot))))
+	http.Handle("/flags", http.HandlerFunc(FlagServer))
+	http.Handle("/args", http.HandlerFunc(ArgServer))
+	http.Handle("/chan", ChanCreate())
+	http.Handle("/date", http.HandlerFunc(DateServer))
+	err := http.ListenAndServe(":12345", nil)
+	if err != nil {
+		log.Panicln("ListenAndServe:", err)
+	}
+}
+
+func Logger(w http.ResponseWriter, req *http.Request) {
+	log.Print(req.URL.String())
+	w.WriteHeader(404)
+	w.Write([]byte("oops"))
+}
+
+func HelloServer(w http.ResponseWriter, req *http.Request) {
+	helloRequests.Add(1)
+	io.WriteString(w, "hello, world!\n")
+}
+
+// This makes Counter satisfy the expvar.Var interface, so we can export
+// it directly.
+func (ctr *Counter) String() string { return fmt.Sprintf("%d", ctr.n) }
+
+func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+	switch req.Method {
+	case "GET": // increment n
+		ctr.n++
+	case "POST": // set n to posted value
+		buf := new(bytes.Buffer)
+		io.Copy(buf, req.Body)
+		body := buf.String()
+		if n, err := strconv.Atoi(body); err != nil {
+			fmt.Fprintf(w, "bad POST: %v\nbody: [%v]\n", err, body)
+		} else {
+			ctr.n = n
+			fmt.Fprint(w, "counter reset\n")
+		}
+	}
+	fmt.Fprintf(w, "counter = %d\n", ctr.n)
+}
+
+func FlagServer(w http.ResponseWriter, req *http.Request) {
+	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
+	fmt.Fprint(w, "Flags:\n")
+	flag.VisitAll(func(f *flag.Flag) {
+		if f.Value.String() != f.DefValue {
+			fmt.Fprintf(w, "%s = %s [default = %s]\n", f.Name, f.Value.String(), f.DefValue)
+		} else {
+			fmt.Fprintf(w, "%s = %s\n", f.Name, f.Value.String())
+		}
+	})
+}
+
+// simple argument server
+func ArgServer(w http.ResponseWriter, req *http.Request) {
+	for _, s := range os.Args {
+		fmt.Fprint(w, s, " ")
+	}
+}
+
+func ChanCreate() Chan {
+	c := make(Chan)
+	go func(c Chan) {
+		for x := 0; ; x++ {
+			c <- x
+		}
+	}(c)
+	return c
+}
+
+func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+	io.WriteString(w, fmt.Sprintf("channel send #%d\n", <-ch))
+}
+
+// exec a program, redirecting output
+func DateServer(rw http.ResponseWriter, req *http.Request) {
+	rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
+	r, w, err := os.Pipe()
+	if err != nil {
+		fmt.Fprintf(rw, "pipe: %s\n", err)
+		return
+	}
+
+	p, err := os.StartProcess("/bin/date", []string{"date"}, &os.ProcAttr{Files: []*os.File{nil, w, w}})
+	defer r.Close()
+	w.Close()
+	if err != nil {
+		fmt.Fprintf(rw, "fork/exec: %s\n", err)
+		return
+	}
+	defer p.Release()
+	io.Copy(rw, r)
+	wait, err := p.Wait()
+	if err != nil {
+		fmt.Fprintf(rw, "wait: %s\n", err)
+		return
+	}
+	if !wait.Exited() {
+		fmt.Fprintf(rw, "date: %v\n", wait)
+		return
+	}
+}

+ 24 - 0
eBook/examples/chapter_15/hello_world_webserver.go

@@ -0,0 +1,24 @@
+// hello_world_webserver.go
+package main
+
+import (
+	"fmt"
+	"net/http"
+	"log"
+)
+
+func HelloServer(w http.ResponseWriter, req *http.Request) {
+	fmt.Println("Inside HelloServer handler")
+	//fmt.Fprint(w, "Hello, " + req.URL.Path[1:])
+	fmt.Fprintf(w, "Hello, %s ", req.URL.Path[1:])
+	// io.WriteString(w, "hello, world!\n")
+}
+
+func main() {
+	http.HandleFunc("/", HelloServer)
+	err := http.ListenAndServe("localhost:8080", nil)
+	if err != nil {
+		log.Fatal("ListenAndServe: ", err.Error())
+	}
+	// http.ListenAndServe(":8080", http.HandlerFunc(HelloServer))
+}

+ 23 - 0
eBook/examples/chapter_15/http_fetch.go

@@ -0,0 +1,23 @@
+// httpfetch.go
+package main
+
+import (
+	"fmt"
+	"net/http"
+	"io/ioutil"
+	"log"
+)
+
+func main() {
+	res, err := http.Get("http://www.google.com")
+	CheckError(err)
+	data, err := ioutil.ReadAll(res.Body)
+	CheckError(err)
+	fmt.Printf("Got: %q", string(data))
+}
+
+func CheckError(err error) {
+	if err != nil {
+		log.Fatalf("Get: %v", err)
+	}
+}

+ 17 - 0
eBook/examples/chapter_15/pipeline1.go

@@ -0,0 +1,17 @@
+// pipeline1.go
+package main
+
+import (
+	"text/template"
+	"os"
+)
+
+func main() {
+	t := template.New("template test")
+	t = template.Must(t.Parse("This is just static text. \n{{\"This is pipeline data - because it is evaluated within the double braces.\"}} {{`So is this, but within reverse quotes.`}}\n"))
+	t.Execute(os.Stdout, nil)
+}
+/*
+This is just static text. 
+This is pipeline data - because it is evaluated within the double braces. So is this, but within reverse quotes.
+*/

+ 30 - 0
eBook/examples/chapter_15/poll_url.go

@@ -0,0 +1,30 @@
+// poll_url.go
+package main
+
+import (
+	"fmt"
+	"net/http"
+)
+
+var urls = []string{
+	"http://www.google.com/",
+	"http://golang.org/",
+	"http://blog.golang.org/",
+}
+
+func main() {
+	// Execute an HTTP HEAD request for all url's 
+	// and returns the HTTP status string or an error string.
+	for _, url := range urls {
+		resp, err := http.Head(url)
+		if err != nil {
+			fmt.Println("Error", url, err)
+		}
+		fmt.Print(url, ": ", resp.Status)
+	}
+}
+/* Output:
+http://www.google.com/ :  302 Found
+http://golang.org/ :  200 OK
+http://blog.golang.org/ :  200 OK
+*/

+ 14 - 0
eBook/examples/chapter_15/predefined_functions.go

@@ -0,0 +1,14 @@
+// predefined_functions.go
+package main
+
+import (
+	"os"
+	"text/template"
+)
+
+func main() {
+	t := template.New("test")
+	t = template.Must(t.Parse("{{with $x := `hello`}}{{printf `%s %s` $x `Mary`}}{{end}}!\n"))
+	t.Execute(os.Stdout, nil)
+}
+// hello Mary!

+ 57 - 0
eBook/examples/chapter_15/robust_webserver.go

@@ -0,0 +1,57 @@
+// robust_webserver.go
+package main  
+  
+import (  
+     "net/http"  
+     "io"  
+	 "log"
+)  
+
+const form = `<html><body><form action="#" method="post" name="bar">
+		      <input type="text" name="in"/>
+			  <input type="submit" value="Submit"/>
+			  </form></html></body>`
+
+type HandleFnc func(http.ResponseWriter,*http.Request)
+
+/* handle a simple get request */  
+func SimpleServer(w http.ResponseWriter, request *http.Request) {  
+     io.WriteString(w, "<h1>hello, world</h1>")  
+}  
+  
+/* handle a form, both the GET which displays the form 
+   and the POST which processes it.*/  
+func FormServer(w http.ResponseWriter, request *http.Request) {
+     w.Header().Set("Content-Type", "text/html")  
+     switch request.Method {  
+            case "GET":  
+                 /* display the form to the user */  
+                 io.WriteString(w, form );  
+            case "POST":  
+                 /* handle the form data, note that ParseForm must 
+                    be called before we can extract form data*/  
+                 //request.ParseForm();  
+                 //io.WriteString(w, request.Form["in"][0])
+				io.WriteString(w, request.FormValue("in")) 
+     }  
+}  
+  
+func main() {  
+     http.HandleFunc("/test1", logPanics(SimpleServer))  
+     http.HandleFunc("/test2", logPanics(FormServer))  
+     if err := http.ListenAndServe(":8088", nil); err != nil {
+		panic(err)
+     }
+}  
+
+func logPanics(function HandleFnc) HandleFnc {
+    return func(writer http.ResponseWriter, request *http.Request) {
+        defer func() {
+            if x := recover(); x != nil {
+                log.Printf("[%v] caught panic: %v", request.RemoteAddr, x)
+            }
+        }()
+        function(writer, request)
+    }
+}
+

+ 39 - 0
eBook/examples/chapter_15/rpc_client.go

@@ -0,0 +1,39 @@
+// rpc_client.go
+// if the server is not started:
+// can't get the server to start, so client stops immediately with error:
+// 2011/08/01 16:08:05 Error dialing:dial tcp :1234: 
+//		The requested address is not valid in its context.
+// with serverAddress = localhost:
+// 2011/08/01 16:09:23 Error dialing:dial tcp 127.0.0.1:1234: 
+//		No connection could be made because the target machine actively refused it.
+package main
+
+import (
+	"fmt"
+	"log"
+	"net/rpc"
+	"./rpc_objects"
+)
+
+const serverAddress = "localhost"
+
+func main() {
+	client, err := rpc.DialHTTP("tcp", serverAddress + ":1234")
+	if err != nil {
+		log.Fatal("Error dialing:", err)
+	}
+	// Synchronous call
+	args := &rpc_objects.Args{7, 8}
+	var reply int
+	err = client.Call("Args.Multiply", args, &reply)
+	if err != nil {
+		log.Fatal("Args error:", err)
+	}
+	fmt.Printf("Args: %d * %d = %d", args.N, args.M, reply)
+}
+/* Output:
+Starting Process E:/Go/GoBoek/code_examples/chapter_14/rpc_client.exe ...
+
+Args: 7 * 8 = 56
+End Process exit status 0
+*/

+ 13 - 0
eBook/examples/chapter_15/rpc_objects.go

@@ -0,0 +1,13 @@
+// rpc_objects.go
+package rpc_objects
+
+import "net"
+
+type Args struct {
+	N, M int
+}
+
+func (t *Args) Multiply(args *Args, reply *int) net.Error {
+	*reply = args.N * args.M
+	return nil
+}

+ 32 - 0
eBook/examples/chapter_15/rpc_server.go

@@ -0,0 +1,32 @@
+// rpc_server.go
+// after client-exits the server shows the message:
+//       1:1234: The specified network name is no longer available.
+//       2011/08/01 16:19:04 rpc: rpc: server cannot decode request: WSARecv tcp 127.0.0.
+package main
+
+import (
+	"net/http"
+	"log"
+	"net"
+	"net/rpc"
+	"time"
+	"./rpc_objects"
+)
+
+func main() {
+	calc := new(rpc_objects.Args)
+	rpc.Register(calc)
+	rpc.HandleHTTP()
+	listener, e := net.Listen("tcp", "localhost:1234")
+	if e != nil {
+		log.Fatal("Starting RPC-server -listen error:", e)
+	}
+	go http.Serve(listener, nil)
+	time.Sleep(1000e9)
+}
+/* Output:
+Starting Process E:/Go/GoBoek/code_examples/chapter_14/rpc_server.exe ...
+
+** after 5 s: **
+End Process exit status 0
+*/

+ 37 - 0
eBook/examples/chapter_15/server.go

@@ -0,0 +1,37 @@
+package main
+
+import (
+	"fmt"
+	"net"
+)
+
+func main() {
+	fmt.Println("Starting the server ...")
+	// create listener:
+	listener, err := net.Listen("tcp", "localhost:50000")
+	if err != nil {
+		fmt.Println("Error listening", err.Error())
+		return // terminate program
+	}
+	// listen and accept connections from clients:
+	for {
+		conn, err := listener.Accept()
+		if err != nil {
+			fmt.Println("Error accepting", err.Error())
+			return // terminate program
+		}
+		go doServerStuff(conn)
+	}
+}
+
+func doServerStuff(conn net.Conn) {
+	for {
+		buf := make([]byte, 512)
+		_, err := conn.Read(buf)
+		if err != nil {
+			fmt.Println("Error reading", err.Error())
+			return // terminate program
+		}
+		fmt.Printf("Received data: %v", string(buf))
+	}
+}

+ 86 - 0
eBook/examples/chapter_15/simple_tcp_server.go

@@ -0,0 +1,86 @@
+/**
+ * Simple multi-thread/multi-core TCP server.
+ *
+*/
+package main
+
+import (
+	"flag"
+	"net"
+	"fmt"
+	"syscall"
+)
+
+const maxRead = 25
+
+func main() {
+	flag.Parse()
+	if flag.NArg() != 2 {
+		panic("usage: host port")
+	}
+	hostAndPort := fmt.Sprintf("%s:%s", flag.Arg(0), flag.Arg(1))
+	listener := initServer(hostAndPort)
+	for {
+		conn, err := listener.Accept()
+		checkError(err, "Accept: ")
+		go connectionHandler(conn)
+	}
+}
+
+func initServer(hostAndPort string) *net.TCPListener {
+	serverAddr, err := net.ResolveTCPAddr("tcp", hostAndPort)
+	checkError(err, "Resolving address:port failed: `" + hostAndPort + "'")
+	listener, err := net.ListenTCP("tcp", serverAddr)
+	checkError(err, "ListenTCP: ")
+	println("Listening to:    ", listener.Addr().String())
+	return listener
+}
+
+func connectionHandler(conn net.Conn) {
+	connFrom := conn.RemoteAddr().String()
+	println("Connection from: ", connFrom)
+	sayHello(conn)
+	for {
+		var ibuf []byte = make([]byte, maxRead + 1)
+		length, err := conn.Read(ibuf[0:maxRead])
+		ibuf[maxRead] = 0 // to prevent overflow
+		switch err {
+		case nil:
+		   handleMsg(length, err, ibuf)
+		case syscall.EAGAIN:  // try again
+		   continue
+		default:
+			    goto DISCONNECT
+		}
+	}
+
+DISCONNECT:
+	err := conn.Close()
+	println("Closed connection: ", connFrom)
+	checkError(err, "Close: ")
+}
+
+func sayHello(to net.Conn) {
+	obuf := []byte{'L', 'e', 't', '\'', 's', ' ', 'G', 'O', '!', '\n'}
+	wrote, err := to.Write(obuf)
+	checkError(err, "Write: wrote " + string(wrote) + " bytes.")
+}
+
+func handleMsg(length int, err error, msg []byte) {
+	if length > 0 {
+		print("<", length, ":")
+		for i := 0; ; i++ {
+			if msg[i] == 0 {
+				break
+			}
+			fmt.Printf("%c", msg[i])
+		}
+		print(">")
+	}
+}
+
+func checkError(error error, info string) {
+	if error != nil {
+		panic("ERROR: " + info + " " + error.Error())  // terminate program
+	}
+}

+ 42 - 0
eBook/examples/chapter_15/simple_webserver.go

@@ -0,0 +1,42 @@
+// simple_webserver.go
+package main  
+  
+import (  
+     "net/http"  
+     "io"  
+)  
+
+const form = `<html><body><form action="#" method="post" name="bar">
+		      <input type="text" name="in"/>
+			  <input type="submit" value="Submit"/>
+			  </form></html></body>`
+
+/* handle a simple get request */  
+func SimpleServer(w http.ResponseWriter, request *http.Request) {  
+     io.WriteString(w, "<h1>hello, world</h1>")  
+}  
+  
+/* handle a form, both the GET which displays the form 
+   and the POST which processes it.*/  
+func FormServer(w http.ResponseWriter, request *http.Request) {
+     w.Header().Set("Content-Type", "text/html")  
+     switch request.Method {  
+            case "GET":  
+                 /* display the form to the user */  
+                 io.WriteString(w, form );  
+            case "POST":  
+                 /* handle the form data, note that ParseForm must 
+                    be called before we can extract form data*/  
+                 //request.ParseForm();  
+                 //io.WriteString(w, request.Form["in"][0])
+				io.WriteString(w, request.FormValue("in")) 
+     }  
+}  
+  
+func main() {  
+     http.HandleFunc("/test1", SimpleServer)  
+     http.HandleFunc("/test2", FormServer)  
+     if err := http.ListenAndServe(":8088", nil); err != nil {
+		panic(err)
+     }
+}  

+ 30 - 0
eBook/examples/chapter_15/smtp.go

@@ -0,0 +1,30 @@
+// smtp.go
+package main
+
+import (
+        "bytes"
+        "log"
+        "net/smtp"
+)
+
+func main() {
+        // Connect to the remote SMTP server.
+        client, err := smtp.Dial("mail.example.com:25")
+        if err != nil {
+                log.Fatal(err)
+        }
+        // Set the sender and recipient.
+        client.Mail("[email protected]")
+        client.Rcpt("[email protected]")
+        // Send the email body.
+        wc, err := client.Data()
+        if err != nil {
+                log.Fatal(err)
+        }
+        defer wc.Close()
+        buf := bytes.NewBufferString("This is the email body.")
+        if _, err = buf.WriteTo(wc); err != nil {
+                log.Fatal(err)
+        }
+}
+

+ 29 - 0
eBook/examples/chapter_15/smtp_auth.go

@@ -0,0 +1,29 @@
+// smtp_auth.go
+package main
+
+import (
+        "log"
+        "net/smtp"
+)
+
+func main() {
+        // Set up authentication information.
+        auth := smtp.PlainAuth(
+                "",
+                "[email protected]",
+                "password",
+                "mail.example.com",
+        )
+        // Connect to the server, authenticate, set the sender and recipient,
+        // and send the email all in one step.
+        err := smtp.SendMail(
+                "mail.example.com:25",
+                auth,
+                "[email protected]",
+                []string{"[email protected]"},
+                []byte("This is the email body."),
+        )
+        if err != nil {
+                log.Fatal(err)
+        }
+}

+ 32 - 0
eBook/examples/chapter_15/socket.go

@@ -0,0 +1,32 @@
+// socket.go
+package main  
+  
+import (  
+  "fmt"  
+  "net"  
+  "io"  
+)  
+  
+func main() {  
+  var (  
+    host = "www.apache.org"  
+    port = "80"  
+    remote = host + ":" + port  
+    msg string = "GET / \n"  
+    data = make([]uint8, 4096)  
+    read = true  
+    count = 0  
+  )  
+  // create the socket  
+  con, err := net.Dial("tcp", remote)   
+  // send our message.  an HTTP GET request in this case   
+  io.WriteString(con, msg)  
+  // read the response from the webserver   
+  for read {  
+    count, err = con.Read(data)  
+    read = (err == nil)  
+    fmt.Printf(string(data[0:count]))  
+  }  
+  
+  con.Close()  
+}  

+ 23 - 0
eBook/examples/chapter_15/template_field.go

@@ -0,0 +1,23 @@
+// template_field.go
+package main
+
+import (
+	"fmt"
+    "os"
+    "text/template"
+)
+
+type Person struct {
+    Name string
+    nonExportedAgeField string 
+}
+
+func main() {
+    t := template.New("hello") 
+    t, _ = t.Parse("hello {{.Name}}!") 
+    p := Person{Name:"Mary", nonExportedAgeField: "31"}
+    if err := t.Execute(os.Stdout, p); err != nil {
+        fmt.Println("There was an error:", err.Error())
+    }
+}
+// Output:   hello Mary!

+ 26 - 0
eBook/examples/chapter_15/template_ifelse.go

@@ -0,0 +1,26 @@
+// template_ifelse.go
+package main
+
+import (
+	"os"
+	"text/template"
+)
+
+func main() {
+	tEmpty := template.New("template test")
+	tEmpty = template.Must(tEmpty.Parse("Empty pipeline if demo: {{if ``}} Will not print. {{end}}\n")) //empty pipeline following if
+	tEmpty.Execute(os.Stdout, nil)
+
+	tWithValue := template.New("template test")
+	tWithValue = template.Must(tWithValue.Parse("Non empty pipeline if demo: {{if `anything`}} Will print. {{end}}\n")) //non empty pipeline following if condition
+	tWithValue.Execute(os.Stdout, nil)
+
+	tIfElse := template.New("template test")
+	tIfElse = template.Must(tIfElse.Parse("if-else demo: {{if `anything`}} Print IF part. {{else}} Print ELSE part.{{end}}\n")) //non empty pipeline following if condition
+	tIfElse.Execute(os.Stdout, nil)
+}
+/* Output:
+Empty pipeline if demo: 
+Non empty pipeline if demo:  Will print. 
+if-else demo:  Print IF part. 
+*/

+ 22 - 0
eBook/examples/chapter_15/template_validation.go

@@ -0,0 +1,22 @@
+// template_validation.go
+package main
+
+import (
+	"text/template"
+	"fmt"
+)
+
+func main() {
+	tOk := template.New("ok")
+	//a valid template, so no panic with Must:
+	template.Must(tOk.Parse("/* and a comment */ some static text: {{ .Name }}")) 
+	fmt.Println("The first one parsed OK.")
+	fmt.Println("The next one ought to fail.")
+	tErr := template.New("error_template")
+	template.Must(tErr.Parse(" some static text {{ .Name }"))
+}
+/* Output:
+The first one parsed OK.
+The next one ought to fail.
+panic: template: error_template:1: unexpected "}" in command
+*/

+ 24 - 0
eBook/examples/chapter_15/template_variables.go

@@ -0,0 +1,24 @@
+// template_variables.go
+package main
+
+import (
+	"os"
+	"text/template"
+)
+
+func main() {
+	t := template.New("test")
+	t = template.Must(t.Parse("{{with $3 := `hello`}}{{$3}}{{end}}!\n"))
+	t.Execute(os.Stdout, nil)
+
+	t = template.Must(t.Parse("{{with $x3 := `hola`}}{{$x3}}{{end}}!\n"))
+	t.Execute(os.Stdout, nil)
+
+	t = template.Must(t.Parse("{{with $x_1 := `hey`}}{{$x_1}} {{.}} {{$x_1}}{{end}}!\n"))
+	t.Execute(os.Stdout, nil)
+}
+/* Output:
+hello!
+hola!
+hey hey hey!
+*/

+ 20 - 0
eBook/examples/chapter_15/template_with_end.go

@@ -0,0 +1,20 @@
+// template_with_end.go
+package main
+
+import (
+	"os"
+	"text/template"
+)
+
+func main() {
+	t := template.New("test")
+	t, _ = t.Parse("{{with `hello`}}{{.}}{{end}}!\n")
+	t.Execute(os.Stdout, nil)
+
+	t, _ = t.Parse("{{with `hello`}}{{.}} {{with `Mary`}}{{.}}{{end}}{{end}}!\n") 
+	t.Execute(os.Stdout, nil)
+}
+/* Output:
+hello!
+hello Mary!
+*/

+ 42 - 0
eBook/examples/chapter_15/twitter_status.go

@@ -0,0 +1,42 @@
+// twitter_status.go
+package main
+
+import (
+	"net/http"
+	"fmt"
+	"encoding/xml"
+	"io/ioutil"
+)
+/* these structs will house the unmarshalled response. 
+   they should be hierarchically shaped like the XML 
+   but can omit irrelevant data. */
+type Status struct {
+	Text string
+}
+
+type User struct {
+	XMLName xml.Name
+	Status  Status
+}
+// var user User
+
+func main() {
+	// perform an HTTP request for the twitter status of user: Googland  
+	resp, _ := http.Get("http://twitter.com/users/Googland.xml")
+	// initialize the structure of the XML response  
+	user := User{xml.Name{"", "user"}, Status{""}}
+	// unmarshal the XML into our structures 
+	defer resp.Body.Close()
+	if body, err := ioutil.ReadAll(resp.Body); err != nil {
+			fmt.Printf("error: %s", err.Error())
+	} else {
+			fmt.Printf("%s ---", body)
+			xml.Unmarshal(body, &user)
+	}
+	fmt.Printf("name: %s ", user.XMLName)
+	fmt.Printf("status: %s", user.Status.Text)
+}
+/* Output:
+status: Robot cars invade California, on orders from Google: Google has been testing self-driving cars ... http://bit.ly/cbtpUN http://retwt.me/97p<exit code="0" msg="process exited normally"/>
+After Go1: no output: name: { user} status:
+*/

+ 29 - 0
eBook/examples/chapter_15/websocket_client.go

@@ -0,0 +1,29 @@
+// websocket_client.go
+package main
+
+import (
+	"fmt"
+	"time"
+	"code.google.com/p/go.net/websocket"
+)
+
+func main() {
+	ws, err := websocket.Dial("ws://localhost:12345/websocket", "",
+		"http://localhost/")
+	if err != nil {
+		panic("Dial: " + err.Error())
+	}
+	go readFromServer(ws)
+	time.Sleep(5e9)
+    ws.Close()
+}
+
+func readFromServer(ws *websocket.Conn) {
+	buf := make([]byte, 1000)
+	for {
+		if _, err := ws.Read(buf); err != nil {
+			fmt.Printf("%s\n", err.Error())
+			break
+		}
+	}
+}

+ 29 - 0
eBook/examples/chapter_15/websocket_server.go

@@ -0,0 +1,29 @@
+// websocket_server.go
+package main
+
+import (
+	"fmt"
+	"net/http"
+	"code.google.com/p/go.net/websocket"
+)
+
+func server(ws *websocket.Conn) {
+	fmt.Printf("new connection\n")
+	buf := make([]byte, 100)
+	for {
+		if _, err := ws.Read(buf); err != nil {
+			fmt.Printf("%s", err.Error())
+			break
+		}
+	}
+	fmt.Printf(" => closing connection\n")
+	ws.Close()
+}
+
+func main() {
+	http.Handle("/websocket", websocket.Handler(server))
+	err := http.ListenAndServe(":12345", nil)
+	if err != nil {
+		panic("ListenAndServe: " + err.Error())
+	}
+}

+ 2 - 0
eBook/examples/chapter_15/wiki/ANewPage.txt

@@ -0,0 +1,2 @@
+Testing input of new page!
+Go Go Go !

+ 1 - 0
eBook/examples/chapter_15/wiki/TestPage.txt

@@ -0,0 +1 @@
+This is a sample Page.

+ 6 - 0
eBook/examples/chapter_15/wiki/edit.html

@@ -0,0 +1,6 @@
+<h1>Editing {{.Title |html}}</h1>
+
+<form action="/save/{{.Title |html}}" method="POST">
+<div><textarea name="body" rows="20" cols="80">{{printf "%s" .Body|html}}</textarea></div>
+<div><input type="submit" value="Save"></div>
+</form>

+ 2 - 0
eBook/examples/chapter_15/wiki/page.txt

@@ -0,0 +1,2 @@
+Hello Go - World !!!
+This works great.

+ 1 - 0
eBook/examples/chapter_15/wiki/page1.txt

@@ -0,0 +1 @@
+This is a test!!

+ 3 - 0
eBook/examples/chapter_15/wiki/page5.txt

@@ -0,0 +1,3 @@
+Page5 is hereby started.
+This is a first addition.
+2nd addition.

+ 5 - 0
eBook/examples/chapter_15/wiki/view.html

@@ -0,0 +1,5 @@
+<h1>{{.Title |html}}</h1>
+
+<p>[<a href="/edit/{{.Title |html}}">edit</a>]</p>
+
+<div>{{printf "%s" .Body |html}}</div>

+ 97 - 0
eBook/examples/chapter_15/wiki/wiki.go

@@ -0,0 +1,97 @@
+package main
+
+import (
+	"io/ioutil"
+	"log"
+	"net/http"
+	"regexp"
+	"text/template"
+)
+
+const lenPath = len("/view/")
+
+var titleValidator = regexp.MustCompile("^[a-zA-Z0-9]+$")
+var templates = make(map[string]*template.Template)
+var err error
+
+type Page struct {
+	Title string
+	Body  []byte
+}
+
+func init() {
+	for _, tmpl := range []string{"edit", "view"} {
+		templates[tmpl] = template.Must(template.ParseFiles(tmpl + ".html"))
+	}
+}
+
+func main() {
+	http.HandleFunc("/view/", makeHandler(viewHandler))
+	http.HandleFunc("/edit/", makeHandler(editHandler))
+	http.HandleFunc("/save/", makeHandler(saveHandler))
+	err := http.ListenAndServe("localhost:8080", nil)
+	if err != nil {
+		log.Fatal("ListenAndServe: ", err.Error())
+	}
+}
+
+func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		title := r.URL.Path[lenPath:]
+		if !titleValidator.MatchString(title) {
+			http.NotFound(w, r)
+			return
+		}
+		fn(w, r, title)
+	}
+}
+
+func viewHandler(w http.ResponseWriter, r *http.Request, title string) {
+	p, err := load(title)
+	if err != nil { // page not found
+		http.Redirect(w, r, "/edit/"+title, http.StatusFound)
+		return
+	}
+	renderTemplate(w, "view", p)
+}
+
+func editHandler(w http.ResponseWriter, r *http.Request, title string) {
+	p, err := load(title)
+	if err != nil {
+		p = &Page{Title: title}
+	}
+	renderTemplate(w, "edit", p)
+}
+
+func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
+	body := r.FormValue("body")
+	p := &Page{Title: title, Body: []byte(body)}
+	err := p.save()
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	http.Redirect(w, r, "/view/"+title, http.StatusFound)
+}
+
+func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
+	err := templates[tmpl].Execute(w, p)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+	}
+}
+
+func (p *Page) save() error {
+	filename := p.Title + ".txt"
+	// file created with read-write permissions for the current user only
+	return ioutil.WriteFile(filename, p.Body, 0600)
+}
+
+func load(title string) (*Page, error) {
+	filename := title + ".txt"
+	body, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return nil, err
+	}
+	return &Page{Title: title, Body: body}, nil
+}

+ 32 - 0
eBook/examples/chapter_15/wiki/wiki_part1.go

@@ -0,0 +1,32 @@
+package main
+
+import (
+	"fmt"
+	"io/ioutil"
+)
+
+type Page struct {
+	Title string
+	Body  []byte
+}
+
+func (p *Page) save() error {
+	filename := p.Title + ".txt"
+	return ioutil.WriteFile(filename, p.Body, 0600)
+}
+
+func load(title string) (*Page, error) {
+	filename := title + ".txt"
+	body, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return nil, err
+	}
+	return &Page{Title: title, Body: body}, nil
+}
+
+func main() {
+	p1 := &Page{Title: "TestPage", Body: []byte("This is a sample Page.")}
+	p1.save()
+	p2, _ := load("TestPage")
+	fmt.Println(string(p2.Body))
+}

+ 39 - 0
eBook/examples/chapter_15/wiki/wiki_part2.go

@@ -0,0 +1,39 @@
+package main
+
+import (
+	"fmt"
+	"net/http"
+	"io/ioutil"
+)
+
+type Page struct {
+	Title string
+	Body  []byte
+}
+
+func (p *Page) save() error {
+	filename := p.Title + ".txt"
+	return ioutil.WriteFile(filename, p.Body, 0600)
+}
+
+func load(title string) (*Page, error) {
+	filename := title + ".txt"
+	body, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return nil, err
+	}
+	return &Page{Title: title, Body: body}, nil
+}
+
+const lenPath = len("/view/")
+
+func viewHandler(w http.ResponseWriter, r *http.Request) {
+	title := r.URL.Path[lenPath:]
+	p, _ := load(title)
+	fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", p.Title, p.Body)
+}
+
+func main() {
+	http.HandleFunc("/view/", viewHandler)
+	http.ListenAndServe(":8080", nil)
+}