0&&(c=b[x](e),b=b[x](0,e)),f<0?b+"?"+d+c:b+"&"+d+c)};var bd="|",dd=function(a,b,c,d,e,f,j,p,n){var q=cd(a,b);q||(q={},a.get(ab)[k](q));q.id_=b;q.affiliation_=c;q.total_=d;q.tax_=e;q.shipping_=f;q.city_=j;q.state_=p;q.country_=n;q.items_=[];return q},ed=function(a,b,c,d,e,f,j){var a=cd(a,b)||dd(a,b,"",0,0,0,"","",""),p;a:{if(a&&a.items_){p=a.items_;for(var n=0;n=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)
+}