diff --git a/README.md b/README.md index d183aeb..5d4363d 100644 --- a/README.md +++ b/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 语言在国内的学习群体,给大家提供更好的学习资源。 diff --git a/eBook/04.4.md b/eBook/04.4.md index 906e8fd..da8b4f5 100644 --- a/eBook/04.4.md +++ b/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) diff --git a/eBook/04.5.md b/eBook/04.5.md new file mode 100644 index 0000000..02bb02d --- /dev/null +++ b/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) \ No newline at end of file diff --git a/eBook/directory.md b/eBook/directory.md index 3052cdf..4411226 100644 --- a/eBook/directory.md +++ b/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) diff --git a/eBook/examples/chapter_15/Codelab Writing Web Applications - The Go Programming Language.htm b/eBook/examples/chapter_15/Codelab Writing Web Applications - The Go Programming Language.htm new file mode 100644 index 0000000..a54b9d5 --- /dev/null +++ b/eBook/examples/chapter_15/Codelab Writing Web Applications - The Go Programming Language.htm @@ -0,0 +1,1027 @@ + + + + + Codelab: Writing Web Applications - The Go Programming Language + + + + + + +
+
+

The Go Programming Language

+ + +
+
+ + +

Codelab: Writing Web Applications

+ + + + + + +

Introduction[Top]

+ +

+Covered in this codelab: +

+ + +

+Assumed knowledge: +

+ + +

Getting Started[Top]

+ +

+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 +VirtualBox or similar) or a +Virtual +Private Server. +

+ +

+Install Go (see the Installation Instructions). +

+ +

+Make a new directory for this codelab and cd to it: +

+ +
$ mkdir ~/gowiki
+$ cd ~/gowiki
+
+ +

+Create a file named wiki.go, open it in your favorite editor, and +add the following lines: +

+ +
package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+)
+
+ +

+We import the fmt, ioutil and os +packages from the Go standard library. Later, as we implement additional +functionality, we will add more packages to this import +declaration. +

+ +

Data Structures[Top]

+ +

+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 Page as a struct with two fields representing +the title and body. +

+ +
type Page struct {
+	Title	string
+	Body	[]byte
+}
+
+ +

+The type []byte means "a byte slice". +(See Effective Go +for more on slices.) +The Body element is a []byte rather than +string because that is the type expected by the io +libraries we will use, as you'll see below. +

+ +

+The Page struct describes how page data will be stored in memory. +But what about persistent storage? We can address that by creating a +save method on Page: +

+ +
func (p *Page) save() os.Error {
+	filename := p.Title + ".txt"
+	return ioutil.WriteFile(filename, p.Body, 0600)
+}
+
+ +

+This method's signature reads: "This is a method named save that +takes as its receiver p, a pointer to Page . It takes +no parameters, and returns a value of type os.Error." +

+ +

+This method will save the Page's Body to a text +file. For simplicity, we will use the Title as the file name. +

+ +

+The save method returns an os.Error value because +that is the return type of WriteFile (a standard library function +that writes a byte slice to a file). The save method returns the +error value, to let the application handle it should anything go wrong while +writing the file. If all goes well, Page.save() will return +nil (the zero-value for pointers, interfaces, and some other +types). +

+ +

+The octal integer constant 0600, passed as the third parameter to +WriteFile, indicates that the file should be created with +read-write permissions for the current user only. (See the Unix man page +open(2) for details.) +

+ +

+We will want to load pages, too: +

+ +
func loadPage(title string) *Page {
+	filename := title + ".txt"
+	body, _ := ioutil.ReadFile(filename)
+	return &Page{Title: title, Body: body}
+}
+
+ +

+The function loadPage constructs the file name from +Title, reads the file's contents into a new +Page, and returns a pointer to that new page. +

+ +

+Functions can return multiple values. The standard library function +io.ReadFile returns []byte and os.Error. +In loadPage, error isn't being handled yet; the "blank identifier" +represented by the underscore (_) symbol is used to throw away the +error return value (in essence, assigning the value to nothing). +

+ +

+But what happens if ReadFile encounters an error? For example, +the file might not exist. We should not ignore such errors. Let's modify the +function to return *Page and os.Error. +

+ +
func loadPage(title string) (*Page, os.Error) {
+	filename := title + ".txt"
+	body, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return nil, err
+	}
+	return &Page{Title: title, Body: body}, nil
+}
+
+ +

+Callers of this function can now check the second parameter; if it is +nil then it has successfully loaded a Page. If not, it will be an +os.Error that can be handled by the caller (see the os package documentation for +details). +

+ +

+At this point we have a simple data structure and the ability to save to and +load from a file. Let's write a main function to test what we've +written: +

+ +
func main() {
+	p1 := &Page{Title: "TestPage", Body: []byte("This is a sample Page.")}
+	p1.save()
+	p2, _ := loadPage("TestPage")
+	fmt.Println(string(p2.Body))
+}
+
+ +

+After compiling and executing this code, a file named TestPage.txt +would be created, containing the contents of p1. The file would +then be read into the struct p2, and its Body element +printed to the screen. +

+ +

+You can compile and run the program like this: +

+ +
$ 8g wiki.go
+$ 8l wiki.8
+$ ./8.out
+This is a sample page.
+
+ +

+(The 8g and 8l commands are applicable to +GOARCH=386. If you're on an amd64 system, +substitute 6's for the 8's.) +

+ +

+Click here to view the code we've written so far. +

+ +

Introducing the http package (an interlude)[Top]

+ +

+Here's a full working example of a simple web server: +

+ +
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)
+}
+
+ +

+The main function begins with a call to +http.HandleFunc, which tells the http package to +handle all requests to the web root ("/") with +handler. +

+ +

+It then calls http.ListenAndServe, specifying that it should +listen on port 8080 on any interface (":8080"). (Don't +worry about its second parameter, nil, for now.) +This function will block until the program is terminated. +

+ +

+The function handler is of the type http.HandlerFunc. +It takes an http.ResponseWriter and an http.Request as +its arguments. +

+ +

+An http.ResponseWriter value assembles the HTTP server's response; by writing +to it, we send data to the HTTP client. +

+ +

+An http.Request is a data structure that represents the client +HTTP request. The string r.URL.Path is the path component +of the request URL. The trailing [1:] means +"create a sub-slice of Path from the 1st character to the end." +This drops the leading "/" from the path name. +

+ +

+If you run this program and access the URL: +

+
http://localhost:8080/monkeys
+

+the program would present a page containing: +

+
Hi there, I love monkeys!
+ +

Using http to serve wiki pages[Top]

+ +

+To use the http package, it must be imported: +

+ +
import (
+	"fmt"
+	"http"
+	"io/ioutil"
+	"os"
+)
+
+ +

+Let's create a handler to view a wiki page: +

+ +
const lenPath = len("/view/")
+
+func viewHandler(w http.ResponseWriter, r *http.Request) {
+	title := r.URL.Path[lenPath:]
+	p, _ := loadPage(title)
+	fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", p.Title, p.Body)
+}
+
+ +

+First, this function extracts the page title from r.URL.Path, +the path component of the request URL. The global constant +lenPath is the length of the leading "/view/" +component of the request path. +The Path is re-sliced with [lenPath:] to drop the +first 6 characters of the string. This is because the path will invariably +begin with "/view/", which is not part of the page title. +

+ +

+The function then loads the page data, formats the page with a string of simple +HTML, and writes it to w, the http.ResponseWriter. +

+ +

+Again, note the use of _ to ignore the os.Error +return value from loadPage. This is done here for simplicity +and generally considered bad practice. We will attend to this later. +

+ +

+To use this handler, we create a main function that +initializes http using the viewHandler to handle +any requests under the path /view/. +

+ +
func main() {
+	http.HandleFunc("/view/", viewHandler)
+	http.ListenAndServe(":8080", nil)
+}
+
+ +

+Click here to view the code we've written so far. +

+ +

+Let's create some page data (as test.txt), compile our code, and +try serving a wiki page: +

+ +
$ echo "Hello world" > test.txt
+$ 8g wiki.go
+$ 8l wiki.8
+$ ./8.out
+
+ +

+With this web server running, a visit to http://localhost:8080/view/test +should show a page titled "test" containing the words "Hello world". +

+ +

Editing Pages[Top]

+ +

+A wiki is not a wiki without the ability to edit pages. Let's create two new +handlers: one named editHandler to display an 'edit page' form, +and the other named saveHandler to save the data entered via the +form. +

+ +

+First, we add them to main(): +

+ +
func main() {
+	http.HandleFunc("/view/", viewHandler)
+	http.HandleFunc("/edit/", editHandler)
+	http.HandleFunc("/save/", saveHandler)
+	http.ListenAndServe(":8080", nil)
+}
+
+ +

+The function editHandler loads the page +(or, if it doesn't exist, create an empty Page struct), +and displays an HTML form. +

+ +
func editHandler(w http.ResponseWriter, r *http.Request) {
+	title := r.URL.Path[lenPath:]
+	p, err := loadPage(title)
+	if err != nil {
+		p = &Page{Title: title}
+	}
+	fmt.Fprintf(w, "<h1>Editing %s</h1>"+
+		"<form action=\"/save/%s\" method=\"POST\">"+
+		"<textarea name=\"body\">%s</textarea><br>"+
+		"<input type=\"submit\" value=\"Save\">"+
+		"</form>",
+		p.Title, p.Title, p.Body)
+}
+
+ +

+This function will work fine, but all that hard-coded HTML is ugly. +Of course, there is a better way. +

+ +

The template package[Top]

+ +

+The template package is part of the Go standard library. We can +use template to keep the HTML in a separate file, allowing +us to change the layout of our edit page without modifying the underlying Go +code. +

+ +

+First, we must add template to the list of imports: +

+ +
import (
+	"http"
+	"io/ioutil"
+	"os"
+	"template"
+)
+
+ +

+Let's create a template file containing the HTML form. +Open a new file named edit.html, and add the following lines: +

+ +
<h1>Editing {Title}</h1>
+
+<form action="/save/{Title}" method="POST">
+<div><textarea name="body" rows="20" cols="80">{Body|html}</textarea></div>
+<div><input type="submit" value="Save"></div>
+</form>
+
+ +

+Modify editHandler to use the template, instead of the hard-coded +HTML: +

+ +
func editHandler(w http.ResponseWriter, r *http.Request) {
+	title := r.URL.Path[lenPath:]
+	p, err := loadPage(title)
+	if err != nil {
+		p = &Page{Title: title}
+	}
+	t, _ := template.ParseFile("edit.html", nil)
+	t.Execute(w, p)
+}
+
+ +

+The function template.ParseFile will read the contents of +edit.html and return a *template.Template. +

+ +

+The method t.Execute replaces all occurrences of +{Title} and {Body} with the values of +p.Title and p.Body, and writes the resultant +HTML to the http.ResponseWriter. +

+ +

+Note that we've used {Body|html} in the above template. +The |html part asks the template engine to pass the value +Body through the html formatter before outputting it, +which escapes HTML characters (such as replacing > with +&gt;). +This will prevent user data from corrupting the form HTML. +

+ +

+Now that we've removed the fmt.Fprintf statement, we can remove +"fmt" from the import list. +

+ +

+While we're working with templates, let's create a template for our +viewHandler called view.html: +

+ +
<h1>{Title}</h1>
+
+<p>[<a href="/edit/{Title}">edit</a>]</p>
+
+<div>{Body}</div>
+
+ +

+Modify viewHandler accordingly: +

+ +
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)
+}
+
+ +

+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: +

+ +
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 = &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)
+}
+
+ +

+The handlers are now shorter and simpler. +

+ +

Handling non-existent pages[Top]

+ +

+What if you visit /view/APageThatDoesntExist? The program will +crash. This is because it ignores the error return value from +loadPage. Instead, if the requested Page doesn't exist, it should +redirect the client to the edit Page so the content may be created: +

+ +
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)
+}
+
+ +

+The http.Redirect function adds an HTTP status code of +http.StatusFound (302) and a Location +header to the HTTP response. +

+ +

Saving Pages[Top]

+ +

+The function saveHandler will handle the form submission. +

+ +
func saveHandler(w http.ResponseWriter, r *http.Request) {
+	title := r.URL.Path[lenPath:]
+	body := r.FormValue("body")
+	p := &Page{Title: title, Body: []byte(body)}
+	p.save()
+	http.Redirect(w, r, "/view/"+title, http.StatusFound)
+}
+
+ +

+The page title (provided in the URL) and the form's only field, +Body, are stored in a new Page. +The save() method is then called to write the data to a file, +and the client is redirected to the /view/ page. +

+ +

+The value returned by FormValue is of type string. +We must convert that value to []byte before it will fit into +the Page struct. We use []byte(body) to perform +the conversion. +

+ +

Error handling[Top]

+ +

+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. +

+ +

+First, let's handle the errors in renderTemplate: +

+ +
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)
+	}
+}
+
+ +

+The http.Error 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. +

+ +

+Now let's fix up saveHandler: +

+ +
func saveHandler(w http.ResponseWriter, r *http.Request) {
+	title, err := getTitle(w, r)
+	if err != nil {
+		return
+	}
+	body := r.FormValue("body")
+	p := &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)
+}
+
+ +

+Any errors that occur during p.save() will be reported +to the user. +

+ +

Template caching[Top]

+ +

+There is an inefficiency in this code: renderTemplate calls +ParseFile every time a page is rendered. +A better approach would be to call ParseFile once for each +template at program initialization, and store the resultant +*Template values in a data structure for later use. +

+ +

+First we create a global map named templates in which to store +our *Template values, keyed by string +(the template name): +

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

+Then we create an init function, which will be called before +main at program initialization. The function +template.MustParseFile is a convenience wrapper around +ParseFile 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. +

+ +
func init() {
+	for _, tmpl := range []string{"edit", "view"} {
+		templates[tmpl] = template.MustParseFile(tmpl+".html", nil)
+	}
+}
+
+ +

+A for loop is used with a range 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. +

+ +

+We then modify our renderTemplate function to call +the Execute method on the appropriate Template from +templates: + +

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)
+	}
+}
+
+ +

Validation[Top]

+ +

+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. +

+ +

+First, add "regexp" to the import list. +Then we can create a global variable to store our validation regexp: +

+ +
var titleValidator = regexp.MustCompile("^[a-zA-Z0-9]+$")
+
+ +

+The function regexp.MustCompile will parse and compile the +regular expression, and return a regexp.Regexp. +MustCompile, like template.MustParseFile, +is distinct from Compile in that it will panic if +the expression compilation fails, while Compile returns an +os.Error as a second parameter. +

+ +

+Now, let's write a function that extracts the title string from the request +URL, and tests it against our TitleValidator expression: +

+ +
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
+}
+
+ +

+If the title is valid, it will be returned along with a nil +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. +

+ +

+Let's put a call to getTitle in each of the handlers: +

+ +
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 = &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 := &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)
+}
+
+ +

Introducing Function Literals and Closures[Top]

+ +

+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 +function +literals provide a powerful means of abstracting functionality +that can help us here. +

+ +

+First, we re-write the function definition of each of the handlers to accept +a title string: +

+ +
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)
+
+ +

+Now let's define a wrapper function that takes a function of the above +type, and returns a function of type http.HandlerFunc +(suitable to be passed to the function http.HandleFunc): +

+ +
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'
+	}
+}
+
+ +

+The returned function is called a closure because it encloses values defined +outside of it. In this case, the variable fn (the single argument +to makeHandler) is enclosed by the closure. The variable +fn will be one of our save, edit, or view handlers. +

+ +

+Now we can take the code from getTitle and use it here +(with some minor modifications): +

+ +
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)
+	}
+}
+
+ +

+The closure returned by makeHandler is a function that takes +an http.ResponseWriter and http.Request (in other +words, an http.HandlerFunc). +The closure extracts the title from the request path, and +validates it with the TitleValidator regexp. If the +title is invalid, an error will be written to the +ResponseWriter using the http.NotFound function. +If the title is valid, the enclosed handler function +fn will be called with the ResponseWriter, +Request, and title as arguments. +

+ +

+Now we can wrap the handler functions with makeHandler in +main, before they are registered with the http +package: +

+ +
func main() {
+	http.HandleFunc("/view/", makeHandler(viewHandler))
+	http.HandleFunc("/edit/", makeHandler(editHandler))
+	http.HandleFunc("/save/", makeHandler(saveHandler))
+	http.ListenAndServe(":8080", nil)
+}
+
+ +

+Finally we remove the calls to getTitle from the handler functions, +making them much simpler: +

+ +
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 = &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.String(), http.StatusInternalServerError)
+		return
+	}
+	http.Redirect(w, r, "/view/"+title, http.StatusFound)
+}
+
+ +

Try it out![Top]

+ +

+Click here to view the final code listing. +

+ +

+Recompile the code, and run the app: +

+ +
$ 8g wiki.go
+$ 8l wiki.8
+$ ./8.out
+
+ +

+Visiting http://localhost:8080/view/ANewPage +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. +

+ +

Other tasks[Top]

+ +

+Here are some simple tasks you might want to tackle on your own: +

+ + + +
+
+

release.r58.1 8699. Except as noted, this content is licensed under a Creative Commons Attribution 3.0 License.

+
+
+ + + + + \ No newline at end of file diff --git a/eBook/examples/chapter_15/Codelab Writing Web Applications - The Go Programming Language_files/all.css b/eBook/examples/chapter_15/Codelab Writing Web Applications - The Go Programming Language_files/all.css new file mode 100644 index 0000000..a985d8f --- /dev/null +++ b/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; +} diff --git a/eBook/examples/chapter_15/Codelab Writing Web Applications - The Go Programming Language_files/ga.js b/eBook/examples/chapter_15/Codelab Writing Web Applications - The Go Programming Language_files/ga.js new file mode 100644 index 0000000..24ce548 --- /dev/null +++ b/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-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=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;f0&&(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=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=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=0){b=b.replace(/\n|\r/g," ");f=0;for(var j=b[r];f2E3&&(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-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;d0&&(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;d0)&&(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=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-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-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;j0&&(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=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-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&&de.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=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('')}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)});})(); diff --git a/eBook/examples/chapter_15/Codelab Writing Web Applications - The Go Programming Language_files/godocs.js b/eBook/examples/chapter_15/Codelab Writing Web Applications - The Go Programming Language_files/godocs.js new file mode 100644 index 0000000..946c4c3 --- /dev/null +++ b/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

This sweet header

. + * 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); + } +} diff --git a/eBook/examples/chapter_15/client.go b/eBook/examples/chapter_15/client.go new file mode 100644 index 0000000..9c6d861 --- /dev/null +++ b/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)) + } +} diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/00changelog.i b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/00changelog.i new file mode 100644 index 0000000..d3a8311 Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/00changelog.i differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/branch b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/branch new file mode 100644 index 0000000..4ad96d5 --- /dev/null +++ b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/branch @@ -0,0 +1 @@ +default diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/cache/branchheads b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/cache/branchheads new file mode 100644 index 0000000..2844416 --- /dev/null +++ b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/cache/branchheads @@ -0,0 +1,2 @@ +844fb91a777b63798d4657e1c40669e8968f79ad 4 +844fb91a777b63798d4657e1c40669e8968f79ad default diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/cache/tags b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/cache/tags new file mode 100644 index 0000000..25f9984 --- /dev/null +++ b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/cache/tags @@ -0,0 +1,2 @@ +4 844fb91a777b63798d4657e1c40669e8968f79ad + diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/dirstate b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/dirstate new file mode 100644 index 0000000..3f0d5e6 Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/dirstate differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/hgrc b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/hgrc new file mode 100644 index 0000000..692ebb4 --- /dev/null +++ b/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 diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/requires b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/requires new file mode 100644 index 0000000..ca69271 --- /dev/null +++ b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/requires @@ -0,0 +1,4 @@ +revlogv1 +store +fncache +dotencode diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/00changelog.i b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/00changelog.i new file mode 100644 index 0000000..aa4cdf8 Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/00changelog.i differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/00manifest.i b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/00manifest.i new file mode 100644 index 0000000..9fb8a62 Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/00manifest.i differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/_a_u_t_h_o_r_s.i b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/_a_u_t_h_o_r_s.i new file mode 100644 index 0000000..c16b17e Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/_a_u_t_h_o_r_s.i differ diff --git a/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 b/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 new file mode 100644 index 0000000..b853cf0 Binary files /dev/null and b/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 differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/_l_i_c_e_n_s_e.i b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/_l_i_c_e_n_s_e.i new file mode 100644 index 0000000..aa52959 Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/_l_i_c_e_n_s_e.i differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/_r_e_a_d_m_e.i b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/_r_e_a_d_m_e.i new file mode 100644 index 0000000..75b7cc9 Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/_r_e_a_d_m_e.i differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/codereview.cfg.i b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/codereview.cfg.i new file mode 100644 index 0000000..833f42f Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/codereview.cfg.i differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/dict/dict.go.i b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/dict/dict.go.i new file mode 100644 index 0000000..402e254 Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/dict/dict.go.i differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/spdy/read.go.i b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/spdy/read.go.i new file mode 100644 index 0000000..8f166d4 Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/spdy/read.go.i differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/spdy/spdy__test.go.i b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/spdy/spdy__test.go.i new file mode 100644 index 0000000..cfd8899 Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/spdy/spdy__test.go.i differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/spdy/types.go.i b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/spdy/types.go.i new file mode 100644 index 0000000..35054ee Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/spdy/types.go.i differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/spdy/write.go.i b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/spdy/write.go.i new file mode 100644 index 0000000..c81b7d5 Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/spdy/write.go.i differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/client.go.i b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/client.go.i new file mode 100644 index 0000000..65d4320 Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/client.go.i differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/hixie.go.i b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/hixie.go.i new file mode 100644 index 0000000..d52174f Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/hixie.go.i differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/hixie__test.go.i b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/hixie__test.go.i new file mode 100644 index 0000000..baf9710 Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/hixie__test.go.i differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/hybi.go.i b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/hybi.go.i new file mode 100644 index 0000000..83339c2 Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/hybi.go.i differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/hybi__test.go.i b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/hybi__test.go.i new file mode 100644 index 0000000..5cdb6cf Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/hybi__test.go.i differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/server.go.i b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/server.go.i new file mode 100644 index 0000000..702fc38 Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/server.go.i differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/websocket.go.i b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/websocket.go.i new file mode 100644 index 0000000..3e421a6 Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/websocket.go.i differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/websocket__test.go.i b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/websocket__test.go.i new file mode 100644 index 0000000..03bae85 Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/websocket/websocket__test.go.i differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/~2ehgignore.i b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/~2ehgignore.i new file mode 100644 index 0000000..3654614 Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/data/~2ehgignore.i differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/fncache b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/fncache new file mode 100644 index 0000000..a0058a5 --- /dev/null +++ b/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 diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/undo b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/undo new file mode 100644 index 0000000..93c2690 Binary files /dev/null and b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/store/undo differ diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/undo.bookmarks b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/undo.bookmarks new file mode 100644 index 0000000..e69de29 diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/undo.branch b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/undo.branch new file mode 100644 index 0000000..331d858 --- /dev/null +++ b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/undo.branch @@ -0,0 +1 @@ +default \ No newline at end of file diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/undo.desc b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/undo.desc new file mode 100644 index 0000000..48a1e39 --- /dev/null +++ b/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 diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/undo.dirstate b/eBook/examples/chapter_15/code.google.com/p/go.net/.hg/undo.dirstate new file mode 100644 index 0000000..e69de29 diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/.hgignore b/eBook/examples/chapter_15/code.google.com/p/go.net/.hgignore new file mode 100644 index 0000000..571db5f --- /dev/null +++ b/eBook/examples/chapter_15/code.google.com/p/go.net/.hgignore @@ -0,0 +1,2 @@ +syntax:glob +last-change diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/AUTHORS b/eBook/examples/chapter_15/code.google.com/p/go.net/AUTHORS new file mode 100644 index 0000000..15167cd --- /dev/null +++ b/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. diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/CONTRIBUTORS b/eBook/examples/chapter_15/code.google.com/p/go.net/CONTRIBUTORS new file mode 100644 index 0000000..1c4577e --- /dev/null +++ b/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. diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/LICENSE b/eBook/examples/chapter_15/code.google.com/p/go.net/LICENSE new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/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. diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/README b/eBook/examples/chapter_15/code.google.com/p/go.net/README new file mode 100644 index 0000000..6b13d8e --- /dev/null +++ b/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. diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/codereview.cfg b/eBook/examples/chapter_15/code.google.com/p/go.net/codereview.cfg new file mode 100644 index 0000000..e3eb47c --- /dev/null +++ b/eBook/examples/chapter_15/code.google.com/p/go.net/codereview.cfg @@ -0,0 +1,2 @@ +defaultcc: golang-dev@googlegroups.com +contributors: http://go.googlecode.com/hg/CONTRIBUTORS diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/dict/dict.go b/eBook/examples/chapter_15/code.google.com/p/go.net/dict/dict.go new file mode 100644 index 0000000..e7f5290 --- /dev/null +++ b/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]) +} diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/spdy/read.go b/eBook/examples/chapter_15/code.google.com/p/go.net/spdy/read.go new file mode 100644 index 0000000..4830a1d --- /dev/null +++ b/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 +} diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/spdy/spdy_test.go b/eBook/examples/chapter_15/code.google.com/p/go.net/spdy/spdy_test.go new file mode 100644 index 0000000..c1cad4b --- /dev/null +++ b/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) + } +} diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/spdy/types.go b/eBook/examples/chapter_15/code.google.com/p/go.net/spdy/types.go new file mode 100644 index 0000000..7c57d36 --- /dev/null +++ b/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 +} diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/spdy/write.go b/eBook/examples/chapter_15/code.google.com/p/go.net/spdy/write.go new file mode 100644 index 0000000..3dd2ca1 --- /dev/null +++ b/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 +} diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/websocket/client.go b/eBook/examples/chapter_15/code.google.com/p/go.net/websocket/client.go new file mode 100644 index 0000000..1b82e9c --- /dev/null +++ b/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} +} diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/websocket/hixie.go b/eBook/examples/chapter_15/code.google.com/p/go.net/websocket/hixie.go new file mode 100644 index 0000000..6d215b9 --- /dev/null +++ b/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 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: 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 fields. + keyNumber1 := getKeyNumber(key1) + keyNumber2 := getKeyNumber(key2) + + // Step 5. get number of spaces in Sec-WebSocket-Key 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) +} diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/websocket/hixie_test.go b/eBook/examples/chapter_15/code.google.com/p/go.net/websocket/hixie_test.go new file mode 100644 index 0000000..8f387dd --- /dev/null +++ b/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) + } +} diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/websocket/hybi.go b/eBook/examples/chapter_15/code.google.com/p/go.net/websocket/hybi.go new file mode 100644 index 0000000..ab18ffc --- /dev/null +++ b/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) +} diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/websocket/hybi_test.go b/eBook/examples/chapter_15/code.google.com/p/go.net/websocket/hybi_test.go new file mode 100644 index 0000000..7976054 --- /dev/null +++ b/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()) + } +} diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/websocket/server.go b/eBook/examples/chapter_15/code.google.com/p/go.net/websocket/server.go new file mode 100644 index 0000000..63f48e2 --- /dev/null +++ b/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) +} diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/websocket/websocket.go b/eBook/examples/chapter_15/code.google.com/p/go.net/websocket/websocket.go new file mode 100644 index 0000000..f7aabc9 --- /dev/null +++ b/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} diff --git a/eBook/examples/chapter_15/code.google.com/p/go.net/websocket/websocket_test.go b/eBook/examples/chapter_15/code.google.com/p/go.net/websocket/websocket_test.go new file mode 100644 index 0000000..27c58a0 --- /dev/null +++ b/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() +} diff --git a/eBook/examples/chapter_15/dial.go b/eBook/examples/chapter_15/dial.go new file mode 100644 index 0000000..29754ab --- /dev/null +++ b/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) + +} diff --git a/eBook/examples/chapter_15/elaborated_webserver.go b/eBook/examples/chapter_15/elaborated_webserver.go new file mode 100644 index 0000000..60c17f2 --- /dev/null +++ b/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 + } +} \ No newline at end of file diff --git a/eBook/examples/chapter_15/hello_world_webserver.go b/eBook/examples/chapter_15/hello_world_webserver.go new file mode 100644 index 0000000..d2414e8 --- /dev/null +++ b/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)) +} diff --git a/eBook/examples/chapter_15/http_fetch.go b/eBook/examples/chapter_15/http_fetch.go new file mode 100644 index 0000000..8b66d62 --- /dev/null +++ b/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) + } +} diff --git a/eBook/examples/chapter_15/pipeline1.go b/eBook/examples/chapter_15/pipeline1.go new file mode 100644 index 0000000..32d74ee --- /dev/null +++ b/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. +*/ \ No newline at end of file diff --git a/eBook/examples/chapter_15/poll_url.go b/eBook/examples/chapter_15/poll_url.go new file mode 100644 index 0000000..2efe5f2 --- /dev/null +++ b/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 +*/ diff --git a/eBook/examples/chapter_15/predefined_functions.go b/eBook/examples/chapter_15/predefined_functions.go new file mode 100644 index 0000000..ec8962e --- /dev/null +++ b/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! \ No newline at end of file diff --git a/eBook/examples/chapter_15/robust_webserver.go b/eBook/examples/chapter_15/robust_webserver.go new file mode 100644 index 0000000..07501fe --- /dev/null +++ b/eBook/examples/chapter_15/robust_webserver.go @@ -0,0 +1,57 @@ +// robust_webserver.go +package main + +import ( + "net/http" + "io" + "log" +) + +const form = `
+ + +
` + +type HandleFnc func(http.ResponseWriter,*http.Request) + +/* handle a simple get request */ +func SimpleServer(w http.ResponseWriter, request *http.Request) { + io.WriteString(w, "

hello, world

") +} + +/* 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) + } +} + diff --git a/eBook/examples/chapter_15/rpc_client.go b/eBook/examples/chapter_15/rpc_client.go new file mode 100644 index 0000000..3d0ea60 --- /dev/null +++ b/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 +*/ diff --git a/eBook/examples/chapter_15/rpc_objects.go b/eBook/examples/chapter_15/rpc_objects.go new file mode 100644 index 0000000..76def53 --- /dev/null +++ b/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 +} \ No newline at end of file diff --git a/eBook/examples/chapter_15/rpc_server.go b/eBook/examples/chapter_15/rpc_server.go new file mode 100644 index 0000000..7184b48 --- /dev/null +++ b/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 +*/ diff --git a/eBook/examples/chapter_15/server.go b/eBook/examples/chapter_15/server.go new file mode 100644 index 0000000..f7938bb --- /dev/null +++ b/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)) + } +} diff --git a/eBook/examples/chapter_15/simple_tcp_server.go b/eBook/examples/chapter_15/simple_tcp_server.go new file mode 100644 index 0000000..79b6c22 --- /dev/null +++ b/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 + } +} diff --git a/eBook/examples/chapter_15/simple_webserver.go b/eBook/examples/chapter_15/simple_webserver.go new file mode 100644 index 0000000..bf17349 --- /dev/null +++ b/eBook/examples/chapter_15/simple_webserver.go @@ -0,0 +1,42 @@ +// simple_webserver.go +package main + +import ( + "net/http" + "io" +) + +const form = `
+ + +
` + +/* handle a simple get request */ +func SimpleServer(w http.ResponseWriter, request *http.Request) { + io.WriteString(w, "

hello, world

") +} + +/* 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) + } +} diff --git a/eBook/examples/chapter_15/smtp.go b/eBook/examples/chapter_15/smtp.go new file mode 100644 index 0000000..58338c1 --- /dev/null +++ b/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("sender@example.org") + client.Rcpt("recipient@example.net") + // 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) + } +} + diff --git a/eBook/examples/chapter_15/smtp_auth.go b/eBook/examples/chapter_15/smtp_auth.go new file mode 100644 index 0000000..91a298b --- /dev/null +++ b/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( + "", + "user@example.com", + "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, + "sender@example.org", + []string{"recipient@example.net"}, + []byte("This is the email body."), + ) + if err != nil { + log.Fatal(err) + } +} diff --git a/eBook/examples/chapter_15/socket.go b/eBook/examples/chapter_15/socket.go new file mode 100644 index 0000000..f5a4d3d --- /dev/null +++ b/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() +} diff --git a/eBook/examples/chapter_15/template_field.go b/eBook/examples/chapter_15/template_field.go new file mode 100644 index 0000000..cac2ada --- /dev/null +++ b/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! \ No newline at end of file diff --git a/eBook/examples/chapter_15/template_ifelse.go b/eBook/examples/chapter_15/template_ifelse.go new file mode 100644 index 0000000..cd47316 --- /dev/null +++ b/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. +*/ diff --git a/eBook/examples/chapter_15/template_validation.go b/eBook/examples/chapter_15/template_validation.go new file mode 100644 index 0000000..0969dfb --- /dev/null +++ b/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 +*/ diff --git a/eBook/examples/chapter_15/template_variables.go b/eBook/examples/chapter_15/template_variables.go new file mode 100644 index 0000000..0e4f5ef --- /dev/null +++ b/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! +*/ diff --git a/eBook/examples/chapter_15/template_with_end.go b/eBook/examples/chapter_15/template_with_end.go new file mode 100644 index 0000000..21fc9f4 --- /dev/null +++ b/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! +*/ \ No newline at end of file diff --git a/eBook/examples/chapter_15/twitter_status.go b/eBook/examples/chapter_15/twitter_status.go new file mode 100644 index 0000000..3157ea6 --- /dev/null +++ b/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 +After Go1: no output: name: { user} status: +*/ \ No newline at end of file diff --git a/eBook/examples/chapter_15/websocket_client.go b/eBook/examples/chapter_15/websocket_client.go new file mode 100644 index 0000000..6f2aad3 --- /dev/null +++ b/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 + } + } +} diff --git a/eBook/examples/chapter_15/websocket_server.go b/eBook/examples/chapter_15/websocket_server.go new file mode 100644 index 0000000..a191420 --- /dev/null +++ b/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()) + } +} diff --git a/eBook/examples/chapter_15/wiki/ANewPage.txt b/eBook/examples/chapter_15/wiki/ANewPage.txt new file mode 100644 index 0000000..e994fea --- /dev/null +++ b/eBook/examples/chapter_15/wiki/ANewPage.txt @@ -0,0 +1,2 @@ +Testing input of new page! +Go Go Go ! \ No newline at end of file diff --git a/eBook/examples/chapter_15/wiki/TestPage.txt b/eBook/examples/chapter_15/wiki/TestPage.txt new file mode 100644 index 0000000..0963b99 --- /dev/null +++ b/eBook/examples/chapter_15/wiki/TestPage.txt @@ -0,0 +1 @@ +This is a sample Page. \ No newline at end of file diff --git a/eBook/examples/chapter_15/wiki/edit.html b/eBook/examples/chapter_15/wiki/edit.html new file mode 100644 index 0000000..34e314a --- /dev/null +++ b/eBook/examples/chapter_15/wiki/edit.html @@ -0,0 +1,6 @@ +

Editing {{.Title |html}}

+ +
+
+
+
\ No newline at end of file diff --git a/eBook/examples/chapter_15/wiki/page.txt b/eBook/examples/chapter_15/wiki/page.txt new file mode 100644 index 0000000..810b9b7 --- /dev/null +++ b/eBook/examples/chapter_15/wiki/page.txt @@ -0,0 +1,2 @@ +Hello Go - World !!! +This works great. diff --git a/eBook/examples/chapter_15/wiki/page1.txt b/eBook/examples/chapter_15/wiki/page1.txt new file mode 100644 index 0000000..eedd24f --- /dev/null +++ b/eBook/examples/chapter_15/wiki/page1.txt @@ -0,0 +1 @@ +This is a test!! \ No newline at end of file diff --git a/eBook/examples/chapter_15/wiki/page5.txt b/eBook/examples/chapter_15/wiki/page5.txt new file mode 100644 index 0000000..22298c7 --- /dev/null +++ b/eBook/examples/chapter_15/wiki/page5.txt @@ -0,0 +1,3 @@ +Page5 is hereby started. +This is a first addition. +2nd addition. \ No newline at end of file diff --git a/eBook/examples/chapter_15/wiki/view.html b/eBook/examples/chapter_15/wiki/view.html new file mode 100644 index 0000000..0233915 --- /dev/null +++ b/eBook/examples/chapter_15/wiki/view.html @@ -0,0 +1,5 @@ +

{{.Title |html}}

+ +

[edit]

+ +
{{printf "%s" .Body |html}}
diff --git a/eBook/examples/chapter_15/wiki/wiki.go b/eBook/examples/chapter_15/wiki/wiki.go new file mode 100644 index 0000000..ee7a76c --- /dev/null +++ b/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 +} diff --git a/eBook/examples/chapter_15/wiki/wiki_part1.go b/eBook/examples/chapter_15/wiki/wiki_part1.go new file mode 100644 index 0000000..aec8c38 --- /dev/null +++ b/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)) +} diff --git a/eBook/examples/chapter_15/wiki/wiki_part2.go b/eBook/examples/chapter_15/wiki/wiki_part2.go new file mode 100644 index 0000000..a0445ba --- /dev/null +++ b/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, "

%s

%s
", p.Title, p.Body) +} + +func main() { + http.HandleFunc("/view/", viewHandler) + http.ListenAndServe(":8080", nil) +}