mirror of
https://github.com/unknwon/the-way-to-go_ZH_CN.git
synced 2025-08-12 02:35:53 +08:00
05.0.md
This commit is contained in:
@@ -8,7 +8,7 @@
|
|||||||
该翻译版本已获得原作者(Ivo Balbaert)本人授权,并表示支持开源事业的发展!
|
该翻译版本已获得原作者(Ivo Balbaert)本人授权,并表示支持开源事业的发展!
|
||||||
|
|
||||||
##翻译进度
|
##翻译进度
|
||||||
4.9 [指针](eBook/04.9.md)
|
第5章:[控制结构](eBook/05.0.md)
|
||||||
|
|
||||||
##支持本书
|
##支持本书
|
||||||
如果你喜欢本书《Go入门指南》,你可以参与到本书的翻译或纠正工作中来,具体请联系【无闻 E-mail:joe2010xtmf#163.com】,一同完善本书并帮助壮大 Go 语言在国内的学习群体,给大家提供更好的学习资源。
|
如果你喜欢本书《Go入门指南》,你可以参与到本书的翻译或纠正工作中来,具体请联系【无闻 E-mail:joe2010xtmf#163.com】,一同完善本书并帮助壮大 Go 语言在国内的学习群体,给大家提供更好的学习资源。
|
||||||
|
@@ -1,23 +1,8 @@
|
|||||||
##啊哦,亲,你看得也太快了。。。还没翻译完呢 0 0
|
|
||||||
要不等到 ***2013 年 6 月 20 日*** 再来看看吧~~
|
|
||||||
|
|
||||||
这里还有一些其它的学习资源噢~
|
|
||||||
|
|
||||||
- [《Go编程基础》](https://github.com/Unknwon/go-fundamental-programming):已更新至 [第12课](https://github.com/Unknwon/go-fundamental-programming/blob/master/lectures/lecture12.md)
|
|
||||||
- [《Go Web编程》](https://github.com/astaxie/build-web-application-with-golang)
|
|
||||||
|
|
||||||
神马?你说你不想学习?那好吧,去逛逛看看行情也行~
|
|
||||||
|
|
||||||
- [Go Walker](http://gowalker.org) **Go 项目文档在线浏览工具**
|
|
||||||
- [Golang中文社区](http://bbs.mygolang.com/forum.php)
|
|
||||||
- [Go语言学习园地](http://studygolang.com/)
|
|
||||||
- [Golang中国](http://golang.tc)
|
|
||||||
|
|
||||||
#5.0 控制结构
|
#5.0 控制结构
|
||||||
到目前为止,我们看到的都是 Go 程序都是从 main() 函数开始执行,然后按顺序执行该函数体中的代码。但我们经常会需要只有在满足一些特定情况时才执行某些代码,也就是说在代码里进行条件判断。针对这种需求,Go 提供了下面这些条件结构和分支结构:
|
到目前为止,我们看到的都是 Go 程序都是从 main() 函数开始执行,然后按顺序执行该函数体中的代码。但我们经常会需要只有在满足一些特定情况时才执行某些代码,也就是说在代码里进行条件判断。针对这种需求,Go 提供了下面这些条件结构和分支结构:
|
||||||
|
|
||||||
if-else 结构
|
if-else 结构
|
||||||
switch-else 结构
|
switch 结构
|
||||||
select 结构,用于 channel 的选择(第 14.4 节)
|
select 结构,用于 channel 的选择(第 14.4 节)
|
||||||
|
|
||||||
可以使用迭代或循环结构来重复执行一次或多次某段代码(任务):
|
可以使用迭代或循环结构来重复执行一次或多次某段代码(任务):
|
||||||
|
@@ -1,3 +1,18 @@
|
|||||||
|
##啊哦,亲,你看得也太快了。。。还没翻译完呢 0 0
|
||||||
|
要不等到 ***2013 年 6 月 24 日*** 再来看看吧~~
|
||||||
|
|
||||||
|
这里还有一些其它的学习资源噢~
|
||||||
|
|
||||||
|
- [《Go编程基础》](https://github.com/Unknwon/go-fundamental-programming):已更新至 [第12课](https://github.com/Unknwon/go-fundamental-programming/blob/master/lectures/lecture12.md)
|
||||||
|
- [《Go Web编程》](https://github.com/astaxie/build-web-application-with-golang)
|
||||||
|
|
||||||
|
神马?你说你不想学习?那好吧,去逛逛看看行情也行~
|
||||||
|
|
||||||
|
- [Go Walker](http://gowalker.org) **Go 项目文档在线浏览工具**
|
||||||
|
- [Golang中文社区](http://bbs.mygolang.com/forum.php)
|
||||||
|
- [Go语言学习园地](http://studygolang.com/)
|
||||||
|
- [Golang中国](http://golang.tc)
|
||||||
|
|
||||||
#5.1 if-else 结构
|
#5.1 if-else 结构
|
||||||
if 是用于测试某个条件(布尔型或逻辑型)的语句,如果该条件成立,则会执行 if 后由大括号括起来的代码块,否则就忽略该代码块继续执行后续的代码。
|
if 是用于测试某个条件(布尔型或逻辑型)的语句,如果该条件成立,则会执行 if 后由大括号括起来的代码块,否则就忽略该代码块继续执行后续的代码。
|
||||||
|
|
||||||
@@ -189,4 +204,4 @@ if value := process(data); value > max {
|
|||||||
##链接
|
##链接
|
||||||
- [目录](directory.md)
|
- [目录](directory.md)
|
||||||
- 上一节:[控制结构](05.0.md)
|
- 上一节:[控制结构](05.0.md)
|
||||||
- 下一节: [TODO](05.2.md)
|
- 下一节: [测试多返回值函数的错误](05.2.md)
|
||||||
|
@@ -38,6 +38,11 @@
|
|||||||
- 4.9 [指针](04.9.md)
|
- 4.9 [指针](04.9.md)
|
||||||
- 第5章:[控制结构](05.0.md)
|
- 第5章:[控制结构](05.0.md)
|
||||||
- 5.1 [if-else 结构](05.1.md)
|
- 5.1 [if-else 结构](05.1.md)
|
||||||
|
- 5.2 [测试多返回值函数的错误](05.2.md)
|
||||||
|
- 5.3 [switch 结构](05.3.md)
|
||||||
|
- 5.4 [for 结构](05.4.md)
|
||||||
|
- 5.5 [Break 与 continue](05.5.md)
|
||||||
|
- 5.6 [标签与 goto](05.6.md)
|
||||||
- 第6章:函数(function)
|
- 第6章:函数(function)
|
||||||
- 第7章:数组(array)与切片(slice)
|
- 第7章:数组(array)与切片(slice)
|
||||||
- 第8章:Maps
|
- 第8章:Maps
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,205 +0,0 @@
|
|||||||
/* General Styles */
|
|
||||||
body {
|
|
||||||
font-family: "Bitstream Vera Sans", Verdana, sans-serif;
|
|
||||||
font-size: 81.25%;
|
|
||||||
line-height: 1.23em;
|
|
||||||
padding: 0;
|
|
||||||
margin: 1.23em;
|
|
||||||
background: white;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: #04a;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
a:visited {
|
|
||||||
color: #04a;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: #a40;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
a:active {
|
|
||||||
color: #c00;
|
|
||||||
}
|
|
||||||
code, pre {
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
pre {
|
|
||||||
background: #F0F0F0;
|
|
||||||
padding: 0.5em 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Top bar */
|
|
||||||
#container {
|
|
||||||
width: 100%;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
#topnav {
|
|
||||||
height: 55px;
|
|
||||||
background: url(/doc/logo.png) no-repeat top left;
|
|
||||||
}
|
|
||||||
a#logo-box {
|
|
||||||
display: block;
|
|
||||||
height: 55px;
|
|
||||||
}
|
|
||||||
h1#title {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
#nav-main {
|
|
||||||
float: right;
|
|
||||||
width: 500px;
|
|
||||||
margin-top: -5px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#nav-main ul {
|
|
||||||
padding-left: 0;
|
|
||||||
margin-left: 0;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
#nav-main li a {
|
|
||||||
display: inline;
|
|
||||||
display: inline-block;
|
|
||||||
padding: .46em .62em .38em .62em;
|
|
||||||
}
|
|
||||||
#nav-main li a:link,
|
|
||||||
#nav-main li a:visited {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
#nav-main li {
|
|
||||||
display: inline;
|
|
||||||
display: inline-block;
|
|
||||||
background: #e6e6e6 url(/doc/button_background.png) repeat-x;
|
|
||||||
border: solid 1px #999;
|
|
||||||
margin-left: -1px;
|
|
||||||
text-shadow: #fff 0 1px 0;
|
|
||||||
box-shadow: 0 1px 1px #ccc;
|
|
||||||
-moz-box-shadow: 0 1px 1px #ccc;
|
|
||||||
-webkit-box-shadow: 0 1px 1px #ccc;
|
|
||||||
}
|
|
||||||
#nav-main li:first-child {
|
|
||||||
-moz-border-top-left-radius: 4px;
|
|
||||||
border-top-left-radius: 4px;
|
|
||||||
-moz-border-bottom-left-radius: 4px;
|
|
||||||
border-bottom-left-radius: 4px;
|
|
||||||
}
|
|
||||||
#nav-main li:last-child {
|
|
||||||
-moz-border-top-right-radius: 4px;
|
|
||||||
border-top-right-radius: 4px;
|
|
||||||
-moz-border-bottom-right-radius: 4px;
|
|
||||||
border-bottom-right-radius: 4px;
|
|
||||||
}
|
|
||||||
#nav-main .quickref {
|
|
||||||
color: #444;
|
|
||||||
}
|
|
||||||
#nav-main .quickref .sep {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
#search {
|
|
||||||
width: 120px;
|
|
||||||
margin-left: 0.5em;
|
|
||||||
}
|
|
||||||
#search.inactive {
|
|
||||||
text-align: center;
|
|
||||||
color: #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Footer */
|
|
||||||
#site-info {
|
|
||||||
position: relative;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#site-info, #site-info a:link, #site-info a:visited {
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Content */
|
|
||||||
#content {
|
|
||||||
clear: both;
|
|
||||||
padding: 0;
|
|
||||||
position: relative;
|
|
||||||
margin-top: 1.5em;
|
|
||||||
margin-bottom: 1.5em;
|
|
||||||
border-top: solid 1px #aaa;
|
|
||||||
border-bottom: solid 1px #aaa;
|
|
||||||
}
|
|
||||||
.left-column {
|
|
||||||
width: 49%;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
.right-column {
|
|
||||||
width: 49%;
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
.end-columns {
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
#content h1 {
|
|
||||||
margin-bottom: -0em;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
#content h2 {
|
|
||||||
border-top: 2px solid #ddd;
|
|
||||||
padding: 8px 0;
|
|
||||||
margin: 1.5em 0 0;
|
|
||||||
}
|
|
||||||
#content .subtitle {
|
|
||||||
margin-top: 1em;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.navtop a {
|
|
||||||
font-weight: normal; font-size: 7pt;
|
|
||||||
float: right; color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Content and Code Highlighting */
|
|
||||||
pre.ebnf, pre.grammar {
|
|
||||||
background: #FFFFE0;
|
|
||||||
}
|
|
||||||
span.ln {
|
|
||||||
font-size: 80%;
|
|
||||||
color: #777777;
|
|
||||||
}
|
|
||||||
span.comment {
|
|
||||||
color: #002090;
|
|
||||||
}
|
|
||||||
span.highlight {
|
|
||||||
background: #FF9900;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
span.highlight-comment {
|
|
||||||
background: #FF9900;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #002090;
|
|
||||||
}
|
|
||||||
span.selection {
|
|
||||||
background: #FFFF00
|
|
||||||
}
|
|
||||||
span.selection-comment {
|
|
||||||
color: #002090;
|
|
||||||
background: #FFFF00
|
|
||||||
}
|
|
||||||
span.selection-highlight {
|
|
||||||
background: #FF9900;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
span.selection-highlight-comment {
|
|
||||||
background: #FF9900;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #002090;
|
|
||||||
}
|
|
||||||
span.alert {
|
|
||||||
color: #D00000;
|
|
||||||
}
|
|
||||||
#nav table {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.detail {
|
|
||||||
padding: 0.25em 1em;
|
|
||||||
background: #F4F4F4;
|
|
||||||
}
|
|
||||||
sup.new {
|
|
||||||
color: red;
|
|
||||||
font-size: 8px;
|
|
||||||
line-height: 0;
|
|
||||||
}
|
|
@@ -1,47 +0,0 @@
|
|||||||
(function(){var g=void 0,h=null,aa=encodeURIComponent,ba=decodeURIComponent,i=Math;function ca(a,b){return a.name=b}var k="push",da="load",l="charAt",ea="value",m="indexOf",fa="match",ga="name",ha="host",o="toString",r="length",s="prototype",t="split",u="stopPropagation",ia="scope",v="location",w="getString",x="substring",ja="navigator",y="join",z="toLowerCase",A;function ka(a,b){switch(b){case 0:return""+a;case 1:return a*1;case 2:return!!a;case 3:return a*1E3}return a}function B(a){return g==a||"-"==a||""==a}function la(a){if(!a||""==a)return"";for(;a&&" \n\r\t"[m](a[l](0))>-1;)a=a[x](1);for(;a&&" \n\r\t"[m](a[l](a[r]-1))>-1;)a=a[x](0,a[r]-1);return a}function ma(a){var b=1,c=0,d;if(!B(a)){b=0;for(d=a[r]-1;d>=0;d--)c=a.charCodeAt(d),b=(b<<6&268435455)+c+(c<<14),c=b&266338304,b=c!=0?b^c>>21:b}return b}function na(){return i.round(i.random()*2147483647)}
|
|
||||||
function oa(){}function C(a,b){return aa instanceof Function?b?encodeURI(a):aa(a):(D(68),escape(a))}function E(a){a=a[t]("+")[y](" ");if(ba instanceof Function)try{return ba(a)}catch(b){D(17)}else D(68);return unescape(a)}var pa=function(a,b,c,d){a.addEventListener?a.addEventListener(b,c,!!d):a.attachEvent&&a.attachEvent("on"+b,c)},qa=function(a,b,c,d){a.removeEventListener?a.removeEventListener(b,c,!!d):a.detachEvent&&a.detachEvent("on"+b,c)};function ra(a){return a&&a[r]>0?a[0]:""}
|
|
||||||
function sa(a){var b=a?a[r]:0;return b>0?a[b-1]:""}var ta=function(){this.prefix="ga.";this.F={}};ta[s].set=function(a,b){this.F[this.prefix+a]=b};ta[s].get=function(a){return this.F[this.prefix+a]};ta[s].contains=function(a){return this.get(a)!==g};function ua(a){a[m]("www.")==0&&(a=a[x](4));return a[z]()}function va(a,b){var c,d={url:a,protocol:"http",host:"",path:"",c:new ta,anchor:""};if(!a)return d;c=a[m]("://");if(c>=0)d.protocol=a[x](0,c),a=a[x](c+3);c=a.search("/|\\?|#");if(c>=0)d.host=a[x](0,c)[z](),a=a[x](c);else return d.host=a[z](),d;c=a[m]("#");if(c>=0)d.anchor=a[x](c+1),a=a[x](0,c);c=a[m]("?");c>=0&&(wa(d.c,a[x](c+1)),a=a[x](0,c));d.anchor&&b&&wa(d.c,d.anchor);a&&a[l](0)=="/"&&(a=a[x](1));d.path=a;return d}
|
|
||||||
function wa(a,b){function c(b,c){a.contains(b)||a.set(b,[]);a.get(b)[k](c)}for(var d=la(b)[t]("&"),e=0;e<d[r];e++)if(d[e]){var f=d[e][m]("=");f<0?c(d[e],"1"):c(d[e][x](0,f),d[e][x](f+1))}}function xa(a,b){if(B(a))return"-";if("["==a[l](0)&&"]"==a[l](a[r]-1))return"-";var c=F.domain;c+=b&&b!="/"?b:"";return a[m](c)==(a[m]("http://")==0?7:a[m]("https://")==0?8:0)?"0":a};var ya=0;function G(a){return(a?"_":"")+ya++}var za=G(),Aa=G(),Ba=G(),H=G(),J=G(),K=G(),L=G(),Ca=G(),Da=G(),Ea=G(),Fa=G(),Ga=G(),Ha=G(),Ia=G(),Ja=G(),Ka=G(),La=G(),Ma=G(),Na=G(),Oa=G(),Pa=G(),Qa=G(),Ra=G(),Sa=G(),Ta=G(),Ua=G(),Va=G(),Wa=G(),Xa=G(),Ya=G(),Za=G(),$a=G(),ab=G(),bb=G(),cb=G();G();
|
|
||||||
var M=G(!0),db=G(),eb=G(),fb=G(),gb=G(),hb=G(),ib=G(),jb=G(),kb=G(),lb=G(),mb=G(),N=G(!0),nb=G(!0),ob=G(!0),rb=G(!0),sb=G(!0),tb=G(!0),ub=G(!0),vb=G(!0),wb=G(!0),xb=G(!0),yb=G(!0),O=G(!0),zb=G(!0),Ab=G(!0),Bb=G(!0),Cb=G(!0),Db=G(!0),Eb=G(!0),Fb=G(!0),Gb=G(!0),Hb=G(!0),Ib=G(!0),Jb=G(!0),Kb=G(!0),Lb=G(!0),Mb=G(),Nb=G();G();var Ob=G(),Pb=G(),Qb=G(),Tb=G(),Ub=G(),Vb=G(),Wb=G(),Xb=G();G();var Yb=G(),Zb=G();var $b=function(){function a(a,c,d){P(Q[s],a,c,d)}R("_getName",Ba,58);R("_getAccount",za,64);R("_visitCode",N,54);R("_getClientInfo",Ia,53,1);R("_getDetectTitle",La,56,1);R("_getDetectFlash",Ja,65,1);R("_getLocalGifPath",Va,57);R("_getServiceMode",Wa,59);S("_setClientInfo",Ia,66,2);S("_setAccount",za,3);S("_setNamespace",Aa,48);S("_setAllowLinker",Fa,11,2);S("_setDetectFlash",Ja,61,2);S("_setDetectTitle",La,62,2);S("_setLocalGifPath",Va,46,0);S("_setLocalServerMode",Wa,92,g,0);S("_setRemoteServerMode",
|
|
||||||
Wa,63,g,1);S("_setLocalRemoteServerMode",Wa,47,g,2);S("_setSampleRate",Ua,45,1);S("_setCampaignTrack",Ka,36,2);S("_setAllowAnchor",Ga,7,2);S("_setCampNameKey",Na,41);S("_setCampContentKey",Sa,38);S("_setCampIdKey",Ma,39);S("_setCampMediumKey",Qa,40);S("_setCampNOKey",Ta,42);S("_setCampSourceKey",Pa,43);S("_setCampTermKey",Ra,44);S("_setCampCIdKey",Oa,37);S("_setCookiePath",L,9,0);S("_setMaxCustomVariables",Xa,0,1);S("_setVisitorCookieTimeout",Ca,28,1);S("_setSessionCookieTimeout",Da,26,1);S("_setCampaignCookieTimeout",
|
|
||||||
Ea,29,1);S("_setReferrerOverride",fb,49);a("_trackPageview",Q[s].ka,1);a("_trackEvent",Q[s].t,4);a("_trackSocial",Q[s].la,104);a("_trackPageLoadTime",Q[s].ja,100);a("_trackTrans",Q[s].ma,18);a("_sendXEvent",Q[s].s,78);a("_createEventTracker",Q[s].S,74);a("_getVersion",Q[s].X,60);a("_setDomainName",Q[s].r,6);a("_setAllowHash",Q[s].ba,8);a("_getLinkerUrl",Q[s].W,52);a("_link",Q[s].link,101);a("_linkByPost",Q[s].aa,102);a("_setTrans",Q[s].ea,20);a("_addTrans",Q[s].L,21);a("_addItem",Q[s].J,19);a("_setTransactionDelim",
|
|
||||||
Q[s].fa,82);a("_setCustomVar",Q[s].ca,10);a("_deleteCustomVar",Q[s].U,35);a("_getVisitorCustomVar",Q[s].Y,50);a("_setXKey",Q[s].ha,83);a("_setXValue",Q[s].ia,84);a("_getXKey",Q[s].Z,76);a("_getXValue",Q[s].$,77);a("_clearXKey",Q[s].P,72);a("_clearXValue",Q[s].Q,73);a("_createXObj",Q[s].T,75);a("_addIgnoredOrganic",Q[s].H,15);a("_clearIgnoredOrganic",Q[s].M,97);a("_addIgnoredRef",Q[s].I,31);a("_clearIgnoredRef",Q[s].N,32);a("_addOrganic",Q[s].K,14);a("_clearOrganic",Q[s].O,70);a("_cookiePathCopy",
|
|
||||||
Q[s].R,30);a("_get",Q[s].V,106);a("_set",Q[s].da,107);a("_addEventListener",Q[s].addEventListener,108);a("_removeEventListener",Q[s].removeEventListener,109);a("_initData",Q[s].l,2);a("_setVar",Q[s].ga,22);S("_setSessionTimeout",Da,27,3);S("_setCookieTimeout",Ea,25,3);S("_setCookiePersistence",Ca,24,1);a("_setAutoTrackOutbound",oa,79);a("_setTrackOutboundSubdomains",oa,81);a("_setHrefExamineLimit",oa,80)},P=function(a,b,c,d){a[b]=function(){D(d);return c.apply(this,arguments)}},R=function(a,b,c,d){Q[s][a]=
|
|
||||||
function(){D(c);return ka(this.a.get(b),d)}},S=function(a,b,c,d,e){Q[s][a]=function(a){D(c);e==g?this.a.set(b,ka(a,d)):this.a.set(b,e)}},ac=function(a,b){return{type:b,target:a,stopPropagation:function(){throw"aborted";}}};var bc=function(a,b){return b!=="/"?!1:(a[m]("www.google.")==0||a[m](".google.")==0||a[m]("google.")==0)&&!(a[m]("google.org")>-1)?!0:!1},cc=function(a){var b=a.get(J),c=a[w](L,"/");bc(b,c)&&a[u]()};var gc=function(){var a={},b={},c=new dc;this.h=function(a,b){c.add(a,b)};var d=new dc;this.d=function(a,b){d.add(a,b)};var e=!1,f=!1,j=!0;this.G=function(){e=!0};this.f=function(a){this[da]();this.set(Mb,a,!0);e=!1;d.execute(this);e=!0;b={};this.i()};this.load=function(){e&&(e=!1,this.na(),ec(this),f||(f=!0,c.execute(this),fc(this),ec(this)),e=!0)};this.i=function(){if(e)if(f)e=!1,fc(this),e=!0;else this[da]()};this.get=function(c){c&&c[l](0)=="_"&&this[da]();return b[c]!==g?b[c]:a[c]};this.set=
|
|
||||||
function(c,d,e){c&&c[l](0)=="_"&&this[da]();e?b[c]=d:a[c]=d;c&&c[l](0)=="_"&&this.i()};this.m=function(b){a[b]=this.b(b,0)+1};this.b=function(a,b){var c=this.get(a);return c==g||c===""?b:c*1};this.getString=function(a,b){var c=this.get(a);return c==g?b:c+""};this.na=function(){if(j){var b=this[w](J,""),c=this[w](L,"/");bc(b,c)||(a[K]=a[Ha]&&b!=""?ma(b):1,j=!1)}}};gc[s].stopPropagation=function(){throw"aborted";};function T(a,b){for(var b=b||[],c=0;c<b[r];c++){var d=b[c];if(""+a==d||d[m](a+".")==0)return d}return"-"}
|
|
||||||
var hc=function(a,b){var c=a.b(K,1),d=b[t](".");if(d[r]!==6||d[0]!=c)return!1;var c=d[1]*1,e=d[2]*1,f=d[3]*1,j=d[4]*1,d=d[5]*1;if(!(c>=0&&e>0&&f>0&&j>0&&d>=0))return D(110),!1;a.set(N,c);a.set(sb,e);a.set(tb,f);a.set(ub,j);a.set(vb,d);return!0},ic=function(a){var b=a.get(N),c=a.get(sb),d=a.get(tb),e=a.get(ub),f=a.b(vb,1);b==g?D(113):b==NaN&&D(114);b>=0&&c>0&&d>0&&e>0&&f>=0||D(115);return[a.b(K,1),b!=g?b:"-",c||"-",d||"-",e||"-",f][y](".")},jc=function(a){return[a.b(K,1),a.b(yb,0),a.b(O,1),a.b(zb,
|
|
||||||
0)][y](".")},kc=function(a,b){var c=b[t]("."),d=a.b(K,1);if(c[r]!==4||c[0]!=d)c=h;a.set(yb,c?c[1]*1:0);a.set(O,c?c[2]*1:10);a.set(zb,c?c[3]*1:a.get(H));return c!=h||b==d},lc=function(a,b){var c=C(a[w](ob,"")),d=[],e=a.get(M);if(!b&&e){for(var f=0;f<e[r];f++){var j=e[f];j&&j[ia]==1&&d[k](f+"="+C(j[ga])+"="+C(j[ea])+"=1")}d[r]>0&&(c+="|"+d[y](","))}return c?a.b(K,1)+"."+c:h},mc=function(a,b){var c=a.b(K,1),d=b[t](".");if(d[r]<2||d[0]!=c)return!1;c=d.slice(1)[y](".")[t]("|");c[r]>0&&a.set(ob,E(c[0]));
|
|
||||||
if(c[r]<=1)return!0;for(var d=c[1][t](","),e=0;e<d[r];e++){var f=d[e][t]("=");if(f[r]==4){var j={};ca(j,E(f[1]));j.value=E(f[2]);j.scope=1;a.get(M)[f[0]]=j}}c[1][m]("^")>=0&&D(125);return!0},oc=function(a,b){var c=nc(a,b);return c?[a.b(K,1),a.b(Ab,0),a.b(Bb,1),a.b(Cb,1),c][y]("."):""},nc=function(a){function b(b,e){if(!B(a.get(b))){var f=a[w](b,""),f=f[t](" ")[y]("%20"),f=f[t]("+")[y]("%20");c[k](e+"="+f)}}var c=[];b(Eb,"utmcid");b(Ib,"utmcsr");b(Gb,"utmgclid");b(Hb,"utmdclid");b(Fb,"utmccn");b(Jb,
|
|
||||||
"utmcmd");b(Kb,"utmctr");b(Lb,"utmcct");return c[y]("|")},qc=function(a,b){var c=a.b(K,1),d=b[t](".");if(d[r]<5||d[0]!=c)return a.set(Ab,g),a.set(Bb,g),a.set(Cb,g),a.set(Eb,g),a.set(Fb,g),a.set(Ib,g),a.set(Jb,g),a.set(Kb,g),a.set(Lb,g),a.set(Gb,g),a.set(Hb,g),!1;a.set(Ab,d[1]*1);a.set(Bb,d[2]*1);a.set(Cb,d[3]*1);pc(a,d.slice(4)[y]("."));return!0},pc=function(a,b){function c(a){return(a=b[fa](a+"=(.*?)(?:\\|utm|$)"))&&a[r]==2?a[1]:g}function d(b,c){c&&(c=e?E(c):c[t]("%20")[y](" "),a.set(b,c))}b[m]("=")==
|
|
||||||
-1&&(b=E(b));var e=c("utmcvr")=="2";d(Eb,c("utmcid"));d(Fb,c("utmccn"));d(Ib,c("utmcsr"));d(Jb,c("utmcmd"));d(Kb,c("utmctr"));d(Lb,c("utmcct"));d(Gb,c("utmgclid"));d(Hb,c("utmdclid"))};var dc=function(){this.q=[]};dc[s].add=function(a,b){this.q[k]({name:a,ua:b})};dc[s].execute=function(a){try{for(var b=0;b<this.q[r];b++)this.q[b].ua.call(U,a)}catch(c){}};function rc(a){a.get(Ua)!=100&&a.get(N)%1E4>=a.get(Ua)*100&&a[u]()}function sc(a){tc()&&a[u]()}function uc(a){F[v].protocol=="file:"&&a[u]()}function vc(a){a.get(eb)||a.set(eb,F.title,!0);a.get(db)||a.set(db,F[v].pathname+F[v].search,!0)};var wc=new function(){var a=[];this.set=function(b){a[b]=!0};this.va=function(){for(var b=[],c=0;c<a[r];c++)a[c]&&(b[i.floor(c/6)]^=1<<c%6);for(c=0;c<b[r];c++)b[c]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"[l](b[c]||0);return b[y]("")+"~"}};function D(a){wc.set(a)};var U=window,F=document,tc=function(){var a=U._gaUserPrefs;return a&&a.ioo&&a.ioo()},xc=function(a,b){setTimeout(a,b)},V=function(a){for(var b=[],c=F.cookie[t](";"),a=RegExp("^\\s*"+a+"=\\s*(.*?)\\s*$"),d=0;d<c[r];d++){var e=c[d][fa](a);e&&b[k](e[1])}return b},X=function(a,b,c,d,e){var f;f=tc()?!1:bc(d,c)?!1:!0;if(f){if(b&&U[ja].userAgent[m]("Firefox")>=0){b=b.replace(/\n|\r/g," ");f=0;for(var j=b[r];f<j;++f){var p=b.charCodeAt(f)&255;if(p==10||p==13)b=b[x](0,f)+"?"+b[x](f+1)}}b&&b[r]>2E3&&(b=b[x](0,
|
|
||||||
2E3),D(69));a=a+"="+b+"; path="+c+"; ";e&&(a+="expires="+(new Date((new Date).getTime()+e)).toGMTString()+"; ");d&&(a+="domain="+d+";");F.cookie=a}};var yc,zc,Ac=function(){if(!yc){var a={},b=U[ja],c=U.screen;a.C=c?c.width+"x"+c.height:"-";a.B=c?c.colorDepth+"-bit":"-";a.language=(b&&(b.language||b.browserLanguage)||"-")[z]();a.javaEnabled=b&&b.javaEnabled()?1:0;a.characterSet=F.characterSet||F.charset||"-";yc=a}},Bc=function(){Ac();for(var a=yc,b=U[ja],a=b.appName+b.version+a.language+b.platform+b.userAgent+a.javaEnabled+a.C+a.B+(F.cookie?F.cookie:"")+(F.referrer?F.referrer:""),b=a[r],c=U.history[r];c>0;)a+=c--^b++;return ma(a)},Cc=function(a){Ac();
|
|
||||||
var b=yc;a.set(hb,b.C);a.set(ib,b.B);a.set(lb,b.language);a.set(mb,b.characterSet);a.set(jb,b.javaEnabled);if(a.get(Ia)&&a.get(Ja)){if(!(b=zc)){var c,d,e;d="ShockwaveFlash";if((b=(b=U[ja])?b.plugins:g)&&b[r]>0)for(c=0;c<b[r]&&!e;c++)d=b[c],d[ga][m]("Shockwave Flash")>-1&&(e=d.description[t]("Shockwave Flash ")[1]);else{d=d+"."+d;try{c=new ActiveXObject(d+".7"),e=c.GetVariable("$version")}catch(f){}if(!e)try{c=new ActiveXObject(d+".6"),e="WIN 6,0,21,0",c.AllowScriptAccess="always",e=c.GetVariable("$version")}catch(j){}if(!e)try{c=
|
|
||||||
new ActiveXObject(d),e=c.GetVariable("$version")}catch(p){}e&&(e=e[t](" ")[1][t](","),e=e[0]+"."+e[1]+" r"+e[2])}b=e?e:"-"}zc=b;a.set(kb,zc)}else a.set(kb,"-")};var Y=function(){P(Y[s],"push",Y[s][k],5);P(Y[s],"_createAsyncTracker",Y[s].wa,33);P(Y[s],"_getAsyncTracker",Y[s].xa,34)};Y[s].wa=function(a,b){return Z.k(a,b||"")};Y[s].xa=function(a){return Z.p(a)};Y[s].push=function(a){for(var b=arguments,c=0,d=0;d<b[r];d++)try{if(typeof b[d]==="function")b[d]();else{var e="",f=b[d][0],j=f.lastIndexOf(".");j>0&&(e=f[x](0,j),f=f[x](j+1));var p=e=="_gat"?Z:e=="_gaq"?Dc:Z.p(e);p[f].apply(p,b[d].slice(1))}}catch(n){c++}return c};var Gc=function(){function a(a,b,c,d){g==f[a]&&(f[a]={});g==f[a][b]&&(f[a][b]=[]);f[a][b][c]=d}function b(a,b,c){if(g!=f[a]&&g!=f[a][b])return f[a][b][c]}function c(a,b){if(g!=f[a]&&g!=f[a][b]){f[a][b]=g;var c=!0,d;for(d=0;d<j[r];d++)if(g!=f[a][j[d]]){c=!1;break}c&&(f[a]=g)}}function d(a){var b="",c=!1,d,e;for(d=0;d<j[r];d++)if(e=a[j[d]],g!=e){c&&(b+=j[d]);for(var c=[],f=g,W=g,W=0;W<e[r];W++)if(g!=e[W]){f="";W!=kd&&g==e[W-1]&&(f+=W[o]()+pb);for(var Ic=e[W],Jc="",qb=g,Rb=g,Sb=g,qb=0;qb<Ic[r];qb++)Rb=
|
|
||||||
Ic[l](qb),Sb=I[Rb],Jc+=g!=Sb?Sb:Rb;f+=Jc;c[k](f)}b+=p+c[y](q)+n;c=!1}else c=!0;return b}var e=this,f=[],j=["k","v"],p="(",n=")",q="*",pb="!",I={"'":"'0"};I[n]="'1";I[q]="'2";I[pb]="'3";var kd=1;e.qa=function(a){return g!=f[a]};e.n=function(){for(var a="",b=0;b<f[r];b++)g!=f[b]&&(a+=b[o]()+d(f[b]));return a};e.pa=function(a){if(a==g)return e.n();for(var b=a.n(),c=0;c<f[r];c++)g!=f[c]&&!a.qa(c)&&(b+=c[o]()+d(f[c]));return b};e.e=function(b,c,d){if(!Ec(d))return!1;a(b,"k",c,d);return!0};e.j=function(b,
|
|
||||||
c,d){if(!Fc(d))return!1;a(b,"v",c,d[o]());return!0};e.w=function(a,c){return b(a,"k",c)};e.z=function(a,c){return b(a,"v",c)};e.u=function(a){c(a,"k")};e.v=function(a){c(a,"v")};P(e,"_setKey",e.e,89);P(e,"_setValue",e.j,90);P(e,"_getKey",e.w,87);P(e,"_getValue",e.z,88);P(e,"_clearKey",e.u,85);P(e,"_clearValue",e.v,86)};function Ec(a){return typeof a=="string"}function Fc(a){return typeof a!="number"&&(g==Number||!(a instanceof Number))||i.round(a)!=a||a==NaN||a==Infinity?!1:!0};var Hc=function(a){var b=U.gaGlobal;a&&!b&&(U.gaGlobal=b={});return b},Kc=function(){var a=Hc(!0).hid;if(a==h)a=na(),Hc(!0).hid=a;return a},Lc=function(a){a.set(gb,Kc());var b=Hc();if(b&&b.dh==a.get(K)){var c=b.sid;c&&(c=="0"&&D(112),a.set(ub,c),a.get(nb)&&a.set(tb,c));b=b.vid;a.get(nb)&&b&&(b=b[t]("."),b[1]*1||D(112),a.set(N,b[0]*1),a.set(sb,b[1]*1))}};var Mc,Nc=function(a,b,c){var d=a[w](J,""),e=a[w](L,"/"),a=a.b(Ca,0);X(b,c,e,d,a)},fc=function(a){var b=a[w](J,"");a.b(K,1);var c=a[w](L,"/");X("__utma",ic(a),c,b,a.get(Ca));X("__utmb",jc(a),c,b,a.get(Da));X("__utmc",""+a.b(K,1),c,b);var d=oc(a,!0);d?X("__utmz",d,c,b,a.get(Ea)):X("__utmz","",c,b,-1);(d=lc(a,!1))?X("__utmv",d,c,b,a.get(Ca)):X("__utmv","",c,b,-1)},ec=function(a){var b=a.b(K,1);if(!hc(a,T(b,V("__utma"))))return a.set(rb,!0),!1;var c=!kc(a,T(b,V("__utmb"))),d=T(b,V("__utmc"))!=a.b(K,
|
|
||||||
1);d&&!c&&D(116);a.set(xb,c||d);qc(a,T(b,V("__utmz")));mc(a,T(b,V("__utmv")));Mc=!c;return!0},Oc=function(a){!Mc&&!(V("__utmb")[r]>0)&&(X("__utmd","1",a[w](L,"/"),a[w](J,""),1E4),V("__utmd")[r]==0&&a[u]())};var Qc=function(a){a.get(N)==g?Pc(a):a.get(rb)&&!a.get(Yb)?Pc(a):a.get(xb)&&(a.set(tb,a.get(ub)),a.set(ub,a.get(H)),a.m(vb),a.set(wb,!0),a.set(yb,0),a.set(O,10),a.set(zb,a.get(H)),a.set(xb,!1))},Pc=function(a){var b=a.get(H);a.set(nb,!0);a.set(N,na()^Bc(a)&2147483647);a.set(ob,"");a.set(sb,b);a.set(tb,b);a.set(ub,b);a.set(vb,1);a.set(wb,!0);a.set(yb,0);a.set(O,10);a.set(zb,b);a.set(M,[]);a.set(rb,!1);a.set(xb,!1)};var Rc="daum:q,eniro:search_word,naver:query,pchome:q,images.google:q,google:q,yahoo:p,yahoo:q,msn:q,bing:q,aol:query,aol:encquery,aol:q,lycos:query,ask:q,altavista:q,netscape:query,cnn:query,about:terms,mamma:q,alltheweb:q,voila:rdata,virgilio:qs,live:q,baidu:wd,alice:qs,yandex:text,najdi:q,mama:query,seznam:q,search:q,wp:szukaj,onet:qt,szukacz:q,yam:k,kvasir:q,sesam:q,ozu:q,terra:query,mynet:q,ekolay:q,rambler:query".split(","),Xc=function(a){if(a.get(Ka)&&!a.get(Yb)){for(var b=!B(a.get(Eb))||!B(a.get(Ib))||
|
|
||||||
!B(a.get(Gb))||!B(a.get(Hb)),c={},d=0;d<Sc[r];d++){var e=Sc[d];c[e]=a.get(e)}d=va(F[v].href,a.get(Ga));if(!(sa(d.c.get(a.get(Ta)))=="1"&&b)&&(!Tc(a,d)&&!Uc(a)&&!b&&a.get(wb)&&a.get(wb)&&Vc(a,g,"(direct)",g,g,"(direct)","(none)",g,g),a.set(Db,Wc(a,c)),b=a.get(Ib)=="(direct)"&&a.get(Fb)=="(direct)"&&a.get(Jb)=="(none)",a.get(Db)||a.get(wb)&&!b))a.set(Ab,a.get(H)),a.set(Bb,a.get(vb)),a.m(Cb)}},Tc=function(a,b){function c(c,d){var d=d||"-",e=sa(b.c.get(a.get(c)));return e&&e!="-"?E(e):d}var d=sa(b.c.get(a.get(Ma)))||
|
|
||||||
"-",e=sa(b.c.get(a.get(Pa)))||"-",f=sa(b.c.get(a.get(Oa)))||"-",j=sa(b.c.get("dclid"))||"-",p=c(Na,"(not set)"),n=c(Qa,"(not set)"),q=c(Ra),pb=c(Sa);if(B(d)&&B(f)&&B(j)&&B(e))return!1;if(B(q)){var I=xa(a.get(fb),a.get(L)),I=va(I,!0);(I=Yc(a,I))&&!B(I[1]&&!I[2])&&(q=I[1])}Vc(a,d,e,f,j,p,n,q,pb);return!0},Uc=function(a){var b=xa(a.get(fb),a.get(L)),c=va(b,!0);if(!(b!=g&&b!=h&&b!=""&&b!="0"&&b!="-"&&b[m]("://")>=0)||c&&c[ha][m]("google")>-1&&c.c.contains("q")&&c.path=="cse")return!1;if((b=Yc(a,c))&&
|
|
||||||
!b[2])return Vc(a,g,b[0],g,g,"(organic)","organic",b[1],g),!0;else if(b)return!1;if(a.get(wb))a:{for(var b=a.get($a),d=ua(c[ha]),e=0;e<b[r];++e)if(d[m](b[e])>-1){a=!1;break a}Vc(a,g,d,g,g,"(referral)","referral",g,"/"+c.path);a=!0}else a=!1;return a},Yc=function(a,b){for(var c=a.get(Ya),d=0;d<c[r];++d){var e=c[d][t](":");if(b[ha][m](e[0][z]())>-1){var f=ra(b.c.get(e[1]));if(f){a:{for(var c=f,d=a.get(Za),c=E(c)[z](),j=0;j<d[r];++j)if(c==d[j]){c=!0;break a}c=!1}return[e[0],f,c]}}}return h},Vc=function(a,
|
|
||||||
b,c,d,e,f,j,p,n){a.set(Eb,b);a.set(Ib,c);a.set(Gb,d);a.set(Hb,e);a.set(Fb,f);a.set(Jb,j);a.set(Kb,p);a.set(Lb,n)},Sc=[Fb,Eb,Gb,Hb,Ib,Jb,Kb,Lb],Wc=function(a,b){for(var c=0;c<Sc[r];c++){var d=Sc[c],e=b[d]||"-",d=a.get(d)||"-";if(e!=d)return!0}return!1};var $c=function(a){Zc(a,F[v].href)?(a.set(Yb,!0),D(12)):a.set(Yb,!1)},Zc=function(a,b){if(!a.get(Fa))return!1;var c=a.b(K,1),d=va(b,a.get(Ga)),e=T(c,d.c.get("__utma")),f=T(c,d.c.get("__utmb")),j=T(c,d.c.get("__utmc")),p=T(c,d.c.get("__utmx")),n=T(c,d.c.get("__utmz")),q=T(c,d.c.get("__utmv")),d=ra(d.c.get("__utmk"));if(ma(""+e+f+j+p+n+q)!=d)return!1;if(!hc(a,e))return e&&e[m](c+".")!=0&&D(126),!1;kc(a,f);a.b(K,1);qc(a,n);mc(a,q);c=E(p);e=a.b(K,1);f=c[t](".");f[r]<2||f[0]!=e||Nc(a,"__utmx",c);return!0},
|
|
||||||
ad=function(a,b,c){var d;a.b(K,1);d=ic(a)||"-";var e=jc(a)||"-",f=""+a.b(K,1)||"-",j=ra(V("__utmx"))||"-",p=oc(a,!1)||"-",a=lc(a,!1)||"-",n=ma(""+d+e+f+j+p+a),q=[];q[k]("__utma="+d);q[k]("__utmb="+e);q[k]("__utmc="+f);q[k]("__utmx="+j);q[k]("__utmz="+p);q[k]("__utmv="+a);q[k]("__utmk="+n);d=q[y]("&");if(!d)return b;e=b[m]("#");return c?e<0?b+"#"+d:b+"&"+d:(c="",f=b[m]("?"),e>0&&(c=b[x](e),b=b[x](0,e)),f<0?b+"?"+d+c:b+"&"+d+c)};var bd="|",dd=function(a,b,c,d,e,f,j,p,n){var q=cd(a,b);q||(q={},a.get(ab)[k](q));q.id_=b;q.affiliation_=c;q.total_=d;q.tax_=e;q.shipping_=f;q.city_=j;q.state_=p;q.country_=n;q.items_=[];return q},ed=function(a,b,c,d,e,f,j){var a=cd(a,b)||dd(a,b,"",0,0,0,"","",""),p;a:{if(a&&a.items_){p=a.items_;for(var n=0;n<p[r];n++)if(p[n].sku_==c){p=p[n];break a}}p=h}n=p||{};n.transId_=b;n.sku_=c;n.name_=d;n.category_=e;n.price_=f;n.quantity_=j;p||a.items_[k](n);return n},cd=function(a,b){for(var c=a.get(ab),
|
|
||||||
d=0;d<c[r];d++)if(c[d].id_==b)return c[d];return h};var fd,gd=function(a){var f;var e;if(!fd){var b;b=F[v].hash;var c=U[ga],d=/^#?gaso=([^&]*)/;if(f=(e=(b=b&&b[fa](d)||c&&c[fa](d))?b[1]:ra(V("GASO")),b=e)&&b[fa](/^(?:\|([-0-9a-z.]{1,30})\|)?([-.\w]{10,1200})$/i),c=f)if(Nc(a,"GASO",""+b),Z._gasoDomain=a.get(J),Z._gasoCPath=a.get(L),b="https://"+((c[1]||"www")+".google.com")+"/analytics/reporting/overlay_js?gaso="+c[2]+"&"+na())a=F.createElement("script"),a.type="text/javascript",a.async=!0,a.src=b,a.id="_gasojs",a.onload=g,b=F.getElementsByTagName("script")[0],
|
|
||||||
b.parentNode.insertBefore(a,b);fd=!0}};var ld=function(a,b){if(a.b(N,0)%100>=a.b(Xb,0))return!1;var c=hd();c==g&&(c=id());if(c==g||c==Infinity||isNaN(c))return!1;c>0?b(jd(c)):pa(U,"load",function(){ld(a,b)},!1);return!0},jd=function(a){var b=new Gc,c=i.min(i.floor(a/100),5E3);b.e(14,1,c>0?c+"00":"0");b.j(14,1,a);return b},hd=function(){var a=U.performance||U.webkitPerformance;return(a=a&&a.timing)&&a.loadEventStart-a.fetchStart},id=function(){if(U.top==U){var a=U.external,b=a&&a.onloadT;a&&!a.isValidLoadTime&&(b=g);b>2147483648&&(b=g);
|
|
||||||
b>0&&a.setPageReadyTime();return b}};var Q=function(a,b,c){function d(a){return function(b){if((b=b.get(Zb)[a])&&b[r])for(var c=ac(e,a),d=0;d<b[r];d++)b[d].call(e,c)}}var e=this;this.a=new gc;this.get=function(a){return this.a.get(a)};this.set=function(a,b,c){this.a.set(a,b,c)};this.set(za,b||"UA-XXXXX-X");this.set(Ba,a||"");this.set(Aa,c||"");this.set(H,i.round((new Date).getTime()/1E3));this.set(L,"/");this.set(Ca,63072E6);this.set(Ea,15768E6);this.set(Da,18E5);this.set(Fa,!1);this.set(Xa,50);this.set(Ga,!1);this.set(Ha,!0);this.set(Ia,
|
|
||||||
!0);this.set(Ja,!0);this.set(Ka,!0);this.set(La,!0);this.set(Na,"utm_campaign");this.set(Ma,"utm_id");this.set(Oa,"gclid");this.set(Pa,"utm_source");this.set(Qa,"utm_medium");this.set(Ra,"utm_term");this.set(Sa,"utm_content");this.set(Ta,"utm_nooverride");this.set(Ua,100);this.set(Xb,10);this.set(Va,"/__utm.gif");this.set(Wa,1);this.set(ab,[]);this.set(M,[]);this.set(Ya,Rc);this.set(Za,[]);this.set($a,[]);this.r("auto");this.set(fb,F.referrer);this.set(Zb,{hit:[],load:[]});this.a.h("0",$c);this.a.h("1",
|
|
||||||
Qc);this.a.h("2",Xc);this.a.h("4",d("load"));this.a.h("5",gd);this.a.d("A",sc);this.a.d("B",uc);this.a.d("C",Qc);this.a.d("D",rc);this.a.d("E",cc);this.a.d("F",md);this.a.d("G",Oc);this.a.d("H",vc);this.a.d("I",Cc);this.a.d("J",Lc);this.a.d("K",d("hit"));this.a.d("L",nd);this.a.d("M",od);this.get(H)===0&&D(111);this.a.G()};A=Q[s];A.g=function(){var a=this.get(bb);a||(a=new Gc,this.set(bb,a));return a};
|
|
||||||
A.oa=function(a){for(var b in a){var c=a[b];a.hasOwnProperty(b)&&typeof c!="function"&&this.set(b,c,!0)}};A.ka=function(a){a&&a!=g&&(a.constructor+"")[m]("String")>-1?(D(13),this.set(db,a,!0)):typeof a==="object"&&a!==h&&this.oa(a);this.a.f("page")};A.t=function(a,b,c,d){if(a==""||!Ec(a)||b==""||!Ec(b))return!1;if(c!=g&&!Ec(c))return!1;if(d!=g&&!Fc(d))return!1;this.set(Ob,a,!0);this.set(Pb,b,!0);this.set(Qb,c,!0);this.set(Tb,d,!0);this.a.f("event");return!0};
|
|
||||||
A.la=function(a,b,c,d){if(!a||!b)return!1;this.set(Ub,a[x](0,15),!0);this.set(Vb,b[x](0,15),!0);this.set(Wb,c||F[v].href,!0);d&&this.set(db,d,!0);this.a.f("social");return!0};A.ja=function(){var a=this;return ld(this.a,function(b){a.s(b)})};A.ma=function(){this.a.f("trans")};A.s=function(a){this.set(cb,a,!0);this.a.f("event")};A.S=function(a){this.l();var b=this;return{_trackEvent:function(c,d,e){D(91);b.t(a,c,d,e)}}};A.V=function(a){return this.get(a)};
|
|
||||||
A.da=function(a,b){if(a)if(a!=g&&(a.constructor+"")[m]("String")>-1)this.set(a,b);else if(typeof a=="object")for(var c in a)a.hasOwnProperty(c)&&this.set(c,a[c])};A.addEventListener=function(a,b){var c=this.get(Zb)[a];c&&c[k](b)};A.removeEventListener=function(a,b){for(var c=this.get(Zb)[a],d=0;c&&d<c[r];d++)if(c[d]==b){c.splice(d,1);break}};A.X=function(){return"5.1.2"};A.r=function(a){this.get(Ha);a=a=="auto"?ua(F.domain):!a||a=="-"||a=="none"?"":a[z]();this.set(J,a)};
|
|
||||||
A.ba=function(a){this.set(Ha,!!a)};A.W=function(a,b){return ad(this.a,a,b)};A.link=function(a,b){if(this.a.get(Fa)&&a){var c=ad(this.a,a,b);F[v].href=c}};A.aa=function(a,b){this.a.get(Fa)&&a&&a.action&&(a.action=ad(this.a,a.action,b))};
|
|
||||||
A.ea=function(){this.l();var a=this.a,b=F.getElementById?F.getElementById("utmtrans"):F.utmform&&F.utmform.utmtrans?F.utmform.utmtrans:h;if(b&&b[ea]){a.set(ab,[]);for(var b=b[ea][t]("UTM:"),c=0;c<b[r];c++){b[c]=la(b[c]);for(var d=b[c][t](bd),e=0;e<d[r];e++)d[e]=la(d[e]);"T"==d[0]?dd(a,d[1],d[2],d[3],d[4],d[5],d[6],d[7],d[8]):"I"==d[0]&&ed(a,d[1],d[2],d[3],d[4],d[5],d[6])}}};A.L=function(a,b,c,d,e,f,j,p){return dd(this.a,a,b,c,d,e,f,j,p)};A.J=function(a,b,c,d,e,f){return ed(this.a,a,b,c,d,e,f)};
|
|
||||||
A.fa=function(a){bd=a||"|"};A.ca=function(a,b,c,d){var e=this.a;if(a<=0||a>e.get(Xa))a=!1;else if(!b||!c||C(b)[r]+C(c)[r]>64)a=!1;else{d!=1&&d!=2&&(d=3);var f={};ca(f,b);f.value=c;f.scope=d;e.get(M)[a]=f;a=!0}a&&this.a.i();return a};A.U=function(a){this.a.get(M)[a]=g;this.a.i()};A.Y=function(a){return(a=this.a.get(M)[a])&&a[ia]==1?a[ea]:g};A.ha=function(a,b,c){this.g().e(a,b,c)};A.ia=function(a,b,c){this.g().j(a,b,c)};A.Z=function(a,b){return this.g().w(a,b)};
|
|
||||||
A.$=function(a,b){return this.g().z(a,b)};A.P=function(a){this.g().u(a)};A.Q=function(a){this.g().v(a)};A.T=function(){return new Gc};A.H=function(a){a&&this.get(Za)[k](a[z]())};A.M=function(){this.set(Za,[])};A.I=function(a){a&&this.get($a)[k](a[z]())};A.N=function(){this.set($a,[])};A.K=function(a,b,c){if(a&&b){var d=this.get(Ya);d.splice(c?0:d[r],0,a+":"+b[z]())}};A.O=function(){this.set(Ya,[])};
|
|
||||||
A.R=function(a){this.a[da]();var b=this.get(L),c=ra(V("__utmx"))||"";this.set(L,a);this.a.i();Nc(this.a,"__utmx",c);this.set(L,b)};A.l=function(){this.a[da]()};A.ga=function(a){a&&a!=""&&(this.set(ob,a),this.a.f("var"))};var md=function(a){a.get(Mb)!=="trans"&&a.b(yb,0)>=500&&a[u]();if(a.get(Mb)==="event"){var b=(new Date).getTime(),c=a.b(zb,0),d=a.b(ub,0),c=i.floor(0.2*((b-(c!=d?c:c*1E3))/1E3));c>0&&(a.set(zb,b),a.set(O,i.min(10,a.b(O,0)+c)));a.b(O,0)<=0&&a[u]()}},od=function(a){a.get(Mb)==="event"&&a.set(O,i.max(0,a.b(O,10)-1))};var pd=function(){var a=[];this.add=function(b,c,d){d&&(c=C(""+c));a[k](b+"="+c)};this.toString=function(){return a[y]("&")}},qd=function(a,b){(b||a.get(Wa)!=2)&&a.m(yb)},rd=function(a,b){b.add("utmwv","5.1.2");b.add("utms",a.get(yb));b.add("utmn",na());var c=F[v].hostname;B(c)||b.add("utmhn",c,!0);c=a.get(Ua);c!=100&&b.add("utmsp",c,!0)},td=function(a,b){b.add("utmac",a.get(za));sd(a,b);Z.o&&b.add("aip",1);b.add("utmu",wc.va())},sd=function(a,b){function c(a,b){b&&d[k](a+"="+b+";")}var d=[];c("__utma",
|
|
||||||
ic(a));c("__utmz",oc(a,!1));c("__utmv",lc(a,!0));c("__utmx",ra(V("__utmx")));b.add("utmcc",d[y]("+"),!0)},ud=function(a,b){a.get(Ia)&&(b.add("utmcs",a.get(mb),!0),b.add("utmsr",a.get(hb)),b.add("utmsc",a.get(ib)),b.add("utmul",a.get(lb)),b.add("utmje",a.get(jb)),b.add("utmfl",a.get(kb),!0))},vd=function(a,b){a.get(La)&&a.get(eb)&&b.add("utmdt",a.get(eb),!0);b.add("utmhid",a.get(gb));b.add("utmr",xa(a.get(fb),a.get(L)),!0);b.add("utmp",C(a.get(db),!0),!0)},wd=function(a,b){for(var c=a.get(bb),d=a.get(cb),
|
|
||||||
e=a.get(M)||[],f=0;f<e[r];f++){var j=e[f];j&&(c||(c=new Gc),c.e(8,f,j[ga]),c.e(9,f,j[ea]),j[ia]!=3&&c.e(11,f,""+j[ia]))}!B(a.get(Ob))&&!B(a.get(Pb))&&(c||(c=new Gc),c.e(5,1,a.get(Ob)),c.e(5,2,a.get(Pb)),e=a.get(Qb),e!=g&&c.e(5,3,e),e=a.get(Tb),e!=g&&c.j(5,1,e));c?b.add("utme",c.pa(d),!0):d&&b.add("utme",d.n(),!0)},xd=function(a,b,c){var d=new pd;qd(a,c);rd(a,d);d.add("utmt","tran");d.add("utmtid",b.id_,!0);d.add("utmtst",b.affiliation_,!0);d.add("utmtto",b.total_,!0);d.add("utmttx",b.tax_,!0);d.add("utmtsp",
|
|
||||||
b.shipping_,!0);d.add("utmtci",b.city_,!0);d.add("utmtrg",b.state_,!0);d.add("utmtco",b.country_,!0);!c&&td(a,d);return d[o]()},yd=function(a,b,c){var d=new pd;qd(a,c);rd(a,d);d.add("utmt","item");d.add("utmtid",b.transId_,!0);d.add("utmipc",b.sku_,!0);d.add("utmipn",b.name_,!0);d.add("utmiva",b.category_,!0);d.add("utmipr",b.price_,!0);d.add("utmiqt",b.quantity_,!0);!c&&td(a,d);return d[o]()},zd=function(a,b){var c=a.get(Mb);if(c=="page")c=new pd,qd(a,b),rd(a,c),wd(a,c),ud(a,c),vd(a,c),!b&&td(a,
|
|
||||||
c),c=[c[o]()];else if(c=="event")c=new pd,qd(a,b),rd(a,c),c.add("utmt","event"),wd(a,c),ud(a,c),vd(a,c),!b&&td(a,c),c=[c[o]()];else if(c=="var")c=new pd,qd(a,b),rd(a,c),c.add("utmt","var"),!b&&td(a,c),c=[c[o]()];else if(c=="trans")for(var c=[],d=a.get(ab),e=0;e<d[r];++e){c[k](xd(a,d[e],b));for(var f=d[e].items_,j=0;j<f[r];++j)c[k](yd(a,f[j],b))}else c=="social"?b?c=[]:(c=new pd,qd(a,b),rd(a,c),c.add("utmt","social"),c.add("utmsn",a.get(Ub),!0),c.add("utmsa",a.get(Vb),!0),c.add("utmsid",a.get(Wb),
|
|
||||||
!0),wd(a,c),ud(a,c),vd(a,c),td(a,c),c=[c[o]()]):c=[];return c},nd=function(a){var b,c=a.get(Nb),d=a.get(Wa);if(d==0||d==2){var e=a.get(Va)+"?";b=zd(a,!0);for(var f=0,j=b[r];f<j;f++)Ad(b[f],d!=2&&f==j-1&&c,e,!0)}if(d==1||d==2){b=zd(a);f=0;for(j=b[r];f<j;f++)try{Ad(b[f],f==j-1&&c)}catch(p){var d=a,e=p,n=new pd;n.add("err",e[ga]);n.add("max",e.message);n.add("len",e.D);n.add("utmwv","5.1.2e");n.add("utmac",d.get(za));n.add("utmn",na());Z.o&&n.add("aip",1);Ad(n[o]())}}};var Bd="https:"==F[v].protocol?"https://ssl.google-analytics.com":"http://www.google-analytics.com",Cd=function(a){ca(this,"len");this.message=8192;this.D=a},Dd=function(a){ca(this,"ff2post");this.message=2036;this.D=a},Ad=function(a,b,c,d){b=b||oa;if(d||a[r]<=2036)Ed(a,b,c);else if(a[r]<=8192){if(U[ja].userAgent[m]("Firefox")>=0&&![].reduce)throw new Dd(a[r]);Fd(a,b)||Gd(a,b)}else throw new Cd(a[r]);},Ed=function(a,b,c){var c=c||Bd+"/__utm.gif?",d=new Image(1,1);d.src=c+a;d.onload=function(){d.onload=
|
|
||||||
h;b()}},Fd=function(a,b){var c,d=Bd+"/p/__utm.gif",e=U.XDomainRequest;if(e)c=new e,c.open("POST",d);else if(e=U.XMLHttpRequest)e=new e,"withCredentials"in e&&(c=e,c.open("POST",d,!0),c.setRequestHeader("Content-Type","text/plain"));if(c)return c.onreadystatechange=function(){c.readyState==4&&(b(),c=h)},c.send(a),!0},Gd=function(a,b){if(F.body){a=aa(a);try{var c=F.createElement('<iframe name="'+a+'"></iframe>')}catch(d){c=F.createElement("iframe"),ca(c,a)}c.height="0";c.width="0";c.style.display="none";
|
|
||||||
c.style.visibility="hidden";var e=F[v],e=Bd+"/u/post_iframe.html#"+aa(e.protocol+"//"+e[ha]+"/favicon.ico"),f=function(){c.src="";c.parentNode&&c.parentNode.removeChild(c)};pa(U,"beforeunload",f);var j=!1,p=0,n=function(){if(!j){try{if(p>9||c.contentWindow[v][ha]==F[v][ha]){j=!0;f();qa(U,"beforeunload",f);b();return}}catch(a){}p++;setTimeout(n,200)}};pa(c,"load",n);F.body.appendChild(c);c.src=e}else xc(function(){Gd(a,b)},100)};var $=function(){this.o=!1;this.A={};this.ra=0;this._gasoCPath=this._gasoDomain=g;P($[s],"_createTracker",$[s].k,55);P($[s],"_getTracker",$[s].ta,0);P($[s],"_getTrackerByName",$[s].p,51);P($[s],"_anonymizeIp",$[s].sa,16);$b()};$[s].ta=function(a,b){return this.k(a,g,b)};$[s].k=function(a,b,c){b&&D(23);c&&D(67);b==g&&(b="~"+Z.ra++);return Z.A[b]=new Q(b,a,c)};$[s].p=function(a){a=a||"";return Z.A[a]||Z.k(g,a)};$[s].sa=function(){this.o=!0};var Hd=function(a){if(F.webkitVisibilityState=="prerender")return!1;a();return!0};var Z=new $;var Id=U._gat;Id&&typeof Id._getTracker=="function"?Z=Id:U._gat=Z;var Dc=new Y;(function(a){if(!Hd(a)){D(123);var b=!1,c=function(){!b&&Hd(a)&&(D(124),b=!0,qa(F,"webkitvisibilitychange",c))};pa(F,"webkitvisibilitychange",c)}})(function(){var a=U._gaq,b=!1;if(a&&typeof a[k]=="function"&&(b=Object[s][o].call(Object(a))=="[object Array]",!b)){Dc=a;return}U._gaq=Dc;b&&Dc[k].apply(Dc,a)});})();
|
|
@@ -1,190 +0,0 @@
|
|||||||
// Except as noted, this content is licensed under Creative Commons
|
|
||||||
// Attribution 3.0
|
|
||||||
|
|
||||||
/* A little code to ease navigation of these documents.
|
|
||||||
*
|
|
||||||
* On window load we:
|
|
||||||
* + Generate a table of contents (godocs_generateTOC)
|
|
||||||
* + Add links up to the top of the doc from each section (godocs_addTopLinks)
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* We want to do some stuff on page load (after the HTML is rendered).
|
|
||||||
So listen for that:
|
|
||||||
*/
|
|
||||||
function bindEvent(el, e, fn) {
|
|
||||||
if (el.addEventListener){
|
|
||||||
el.addEventListener(e, fn, false);
|
|
||||||
} else if (el.attachEvent){
|
|
||||||
el.attachEvent('on'+e, fn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bindEvent(window, 'load', godocs_onload);
|
|
||||||
|
|
||||||
function godocs_onload() {
|
|
||||||
godocs_bindSearchEvents();
|
|
||||||
godocs_generateTOC();
|
|
||||||
godocs_addTopLinks();
|
|
||||||
}
|
|
||||||
|
|
||||||
function godocs_bindSearchEvents() {
|
|
||||||
var search = document.getElementById('search');
|
|
||||||
if (!search) {
|
|
||||||
// no search box (index disabled)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
function clearInactive() {
|
|
||||||
if (search.className == "inactive") {
|
|
||||||
search.value = "";
|
|
||||||
search.className = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function restoreInactive() {
|
|
||||||
if (search.value != "") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (search.type != "search") {
|
|
||||||
search.value = search.getAttribute("placeholder");
|
|
||||||
}
|
|
||||||
search.className = "inactive";
|
|
||||||
}
|
|
||||||
restoreInactive();
|
|
||||||
bindEvent(search, 'focus', clearInactive);
|
|
||||||
bindEvent(search, 'blur', restoreInactive);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Generates a table of contents: looks for h2 and h3 elements and generates
|
|
||||||
* links. "Decorates" the element with id=="nav" with this table of contents.
|
|
||||||
*/
|
|
||||||
function godocs_generateTOC() {
|
|
||||||
var navbar = document.getElementById('nav');
|
|
||||||
if (!navbar) { return; }
|
|
||||||
|
|
||||||
var toc_items = [];
|
|
||||||
|
|
||||||
var i;
|
|
||||||
for (i = 0; i < navbar.parentNode.childNodes.length; i++) {
|
|
||||||
var node = navbar.parentNode.childNodes[i];
|
|
||||||
if ((node.tagName == 'h2') || (node.tagName == 'H2')) {
|
|
||||||
if (!node.id) {
|
|
||||||
node.id = 'tmp_' + i;
|
|
||||||
}
|
|
||||||
var text = godocs_nodeToText(node);
|
|
||||||
if (!text) { continue; }
|
|
||||||
|
|
||||||
var textNode = document.createTextNode(text);
|
|
||||||
|
|
||||||
var link = document.createElement('a');
|
|
||||||
link.href = '#' + node.id;
|
|
||||||
link.appendChild(textNode);
|
|
||||||
|
|
||||||
// Then create the item itself
|
|
||||||
var item = document.createElement('dt');
|
|
||||||
|
|
||||||
item.appendChild(link);
|
|
||||||
toc_items.push(item);
|
|
||||||
}
|
|
||||||
if ((node.tagName == 'h3') || (node.tagName == 'H3')) {
|
|
||||||
if (!node.id) {
|
|
||||||
node.id = 'tmp_' + i;
|
|
||||||
}
|
|
||||||
var text = godocs_nodeToText(node);
|
|
||||||
if (!text) { continue; }
|
|
||||||
|
|
||||||
var textNode = document.createTextNode(text);
|
|
||||||
|
|
||||||
var link = document.createElement('a');
|
|
||||||
link.href = '#' + node.id;
|
|
||||||
link.appendChild(textNode);
|
|
||||||
|
|
||||||
// Then create the item itself
|
|
||||||
var item = document.createElement('dd');
|
|
||||||
|
|
||||||
item.appendChild(link);
|
|
||||||
toc_items.push(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toc_items.length <= 1) { return; }
|
|
||||||
|
|
||||||
var dl1 = document.createElement('dl');
|
|
||||||
var dl2 = document.createElement('dl');
|
|
||||||
|
|
||||||
var split_index = (toc_items.length / 2) + 1;
|
|
||||||
if (split_index < 8) {
|
|
||||||
split_index = toc_items.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < split_index; i++) {
|
|
||||||
dl1.appendChild(toc_items[i]);
|
|
||||||
}
|
|
||||||
for (/* keep using i */; i < toc_items.length; i++) {
|
|
||||||
dl2.appendChild(toc_items[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var tocTable = document.createElement('table');
|
|
||||||
navbar.appendChild(tocTable);
|
|
||||||
tocTable.className = 'unruled';
|
|
||||||
var tocBody = document.createElement('tbody');
|
|
||||||
tocTable.appendChild(tocBody);
|
|
||||||
|
|
||||||
var tocRow = document.createElement('tr');
|
|
||||||
tocBody.appendChild(tocRow);
|
|
||||||
|
|
||||||
// 1st column
|
|
||||||
var tocCell = document.createElement('td');
|
|
||||||
tocCell.className = 'first';
|
|
||||||
tocRow.appendChild(tocCell);
|
|
||||||
tocCell.appendChild(dl1);
|
|
||||||
|
|
||||||
// 2nd column
|
|
||||||
tocCell = document.createElement('td');
|
|
||||||
tocRow.appendChild(tocCell);
|
|
||||||
tocCell.appendChild(dl2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Returns the "This sweet header" from <h2>This <i>sweet</i> header</h2>.
|
|
||||||
* Takes a node, returns a string.
|
|
||||||
*/
|
|
||||||
function godocs_nodeToText(node) {
|
|
||||||
var TEXT_NODE = 3; // Defined in Mozilla but not MSIE :(
|
|
||||||
|
|
||||||
var text = '';
|
|
||||||
for (var j = 0; j != node.childNodes.length; j++) {
|
|
||||||
var child = node.childNodes[j];
|
|
||||||
if (child.nodeType == TEXT_NODE) {
|
|
||||||
if (child.nodeValue != '[Top]') { //ok, that's a hack, but it works.
|
|
||||||
text = text + child.nodeValue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
text = text + godocs_nodeToText(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* For each H2 heading, add a link up to the #top of the document.
|
|
||||||
* (As part of this: ensure existence of 'top' named anchor link
|
|
||||||
* (theoretically at doc's top).)
|
|
||||||
*/
|
|
||||||
function godocs_addTopLinks() {
|
|
||||||
/* Make sure there's a "top" to link to. */
|
|
||||||
var top = document.getElementById('top');
|
|
||||||
if (!top) {
|
|
||||||
document.body.id = 'top';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!document.getElementsByTagName) return; // no browser support
|
|
||||||
|
|
||||||
var headers = document.getElementsByTagName('h2');
|
|
||||||
|
|
||||||
for (var i = 0; i < headers.length; i++) {
|
|
||||||
var span = document.createElement('span');
|
|
||||||
span.className = 'navtop';
|
|
||||||
var link = document.createElement('a');
|
|
||||||
span.appendChild(link);
|
|
||||||
link.href = '#top';
|
|
||||||
var textNode = document.createTextNode('[Top]');
|
|
||||||
link.appendChild(textNode);
|
|
||||||
headers[i].appendChild(span);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,36 +0,0 @@
|
|||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
default
|
|
@@ -1,2 +0,0 @@
|
|||||||
844fb91a777b63798d4657e1c40669e8968f79ad 4
|
|
||||||
844fb91a777b63798d4657e1c40669e8968f79ad default
|
|
@@ -1,2 +0,0 @@
|
|||||||
4 844fb91a777b63798d4657e1c40669e8968f79ad
|
|
||||||
|
|
Binary file not shown.
@@ -1,2 +0,0 @@
|
|||||||
[paths]
|
|
||||||
default = https://code.google.com/p/go.net
|
|
@@ -1,4 +0,0 @@
|
|||||||
revlogv1
|
|
||||||
store
|
|
||||||
fncache
|
|
||||||
dotencode
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,19 +0,0 @@
|
|||||||
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
|
|
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
default
|
|
@@ -1,3 +0,0 @@
|
|||||||
0
|
|
||||||
pull
|
|
||||||
https://code.google.com/p/go.net
|
|
@@ -1,2 +0,0 @@
|
|||||||
syntax:glob
|
|
||||||
last-change
|
|
@@ -1,3 +0,0 @@
|
|||||||
# 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.
|
|
@@ -1,3 +0,0 @@
|
|||||||
# 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.
|
|
@@ -1,27 +0,0 @@
|
|||||||
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.
|
|
@@ -1,3 +0,0 @@
|
|||||||
This repository holds supplementary Go networking libraries.
|
|
||||||
|
|
||||||
To submit changes to this repository, see http://golang.org/doc/contribute.html.
|
|
@@ -1,2 +0,0 @@
|
|||||||
defaultcc: golang-dev@googlegroups.com
|
|
||||||
contributors: http://go.googlecode.com/hg/CONTRIBUTORS
|
|
@@ -1,210 +0,0 @@
|
|||||||
// 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])
|
|
||||||
}
|
|
@@ -1,312 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
@@ -1,497 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,369 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
@@ -1,285 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
@@ -1,137 +0,0 @@
|
|||||||
// 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}
|
|
||||||
}
|
|
@@ -1,695 +0,0 @@
|
|||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package websocket
|
|
||||||
|
|
||||||
// This file implements a protocol of Hixie draft version 75 and 76
|
|
||||||
// (draft 76 equals to hybi 00)
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"crypto/md5"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// An aray of characters to be randomly inserted to construct Sec-WebSocket-Key
|
|
||||||
// value. It holds characters from ranges U+0021 to U+002F and U+003A to U+007E.
|
|
||||||
// See Step 21 in Section 4.1 Opening handshake.
|
|
||||||
// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00#page-22
|
|
||||||
var secKeyRandomChars [0x30 - 0x21 + 0x7F - 0x3A]byte
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
i := 0
|
|
||||||
for ch := byte(0x21); ch < 0x30; ch++ {
|
|
||||||
secKeyRandomChars[i] = ch
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
for ch := byte(0x3a); ch < 0x7F; ch++ {
|
|
||||||
secKeyRandomChars[i] = ch
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type byteReader interface {
|
|
||||||
ReadByte() (byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// readHixieLength reads frame length for frame type 0x80-0xFF
|
|
||||||
// as defined in Hixie draft.
|
|
||||||
// See section 4.2 Data framing.
|
|
||||||
// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00#section-4.2
|
|
||||||
func readHixieLength(r byteReader) (length int64, lengthFields []byte, err error) {
|
|
||||||
for {
|
|
||||||
c, err := r.ReadByte()
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
lengthFields = append(lengthFields, c)
|
|
||||||
length = length*128 + int64(c&0x7f)
|
|
||||||
if c&0x80 == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// A hixieLengthFrameReader is a reader for frame type 0x80-0xFF
|
|
||||||
// as defined in hixie draft.
|
|
||||||
type hixieLengthFrameReader struct {
|
|
||||||
reader io.Reader
|
|
||||||
FrameType byte
|
|
||||||
Length int64
|
|
||||||
header *bytes.Buffer
|
|
||||||
length int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (frame *hixieLengthFrameReader) Read(msg []byte) (n int, err error) {
|
|
||||||
return frame.reader.Read(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (frame *hixieLengthFrameReader) PayloadType() byte {
|
|
||||||
if frame.FrameType == '\xff' && frame.Length == 0 {
|
|
||||||
return CloseFrame
|
|
||||||
}
|
|
||||||
return UnknownFrame
|
|
||||||
}
|
|
||||||
|
|
||||||
func (frame *hixieLengthFrameReader) HeaderReader() io.Reader {
|
|
||||||
if frame.header == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if frame.header.Len() == 0 {
|
|
||||||
frame.header = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return frame.header
|
|
||||||
}
|
|
||||||
|
|
||||||
func (frame *hixieLengthFrameReader) TrailerReader() io.Reader { return nil }
|
|
||||||
|
|
||||||
func (frame *hixieLengthFrameReader) Len() (n int) { return frame.length }
|
|
||||||
|
|
||||||
// A HixieSentinelFrameReader is a reader for frame type 0x00-0x7F
|
|
||||||
// as defined in hixie draft.
|
|
||||||
type hixieSentinelFrameReader struct {
|
|
||||||
reader *bufio.Reader
|
|
||||||
FrameType byte
|
|
||||||
header *bytes.Buffer
|
|
||||||
data []byte
|
|
||||||
seenTrailer bool
|
|
||||||
trailer *bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (frame *hixieSentinelFrameReader) Read(msg []byte) (n int, err error) {
|
|
||||||
if len(frame.data) == 0 {
|
|
||||||
if frame.seenTrailer {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
frame.data, err = frame.reader.ReadSlice('\xff')
|
|
||||||
if err == nil {
|
|
||||||
frame.seenTrailer = true
|
|
||||||
frame.data = frame.data[:len(frame.data)-1] // trim \xff
|
|
||||||
frame.trailer = bytes.NewBuffer([]byte{0xff})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
n = copy(msg, frame.data)
|
|
||||||
frame.data = frame.data[n:]
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (frame *hixieSentinelFrameReader) PayloadType() byte {
|
|
||||||
if frame.FrameType == 0 {
|
|
||||||
return TextFrame
|
|
||||||
}
|
|
||||||
return UnknownFrame
|
|
||||||
}
|
|
||||||
|
|
||||||
func (frame *hixieSentinelFrameReader) HeaderReader() io.Reader {
|
|
||||||
if frame.header == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if frame.header.Len() == 0 {
|
|
||||||
frame.header = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return frame.header
|
|
||||||
}
|
|
||||||
|
|
||||||
func (frame *hixieSentinelFrameReader) TrailerReader() io.Reader {
|
|
||||||
if frame.trailer == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if frame.trailer.Len() == 0 {
|
|
||||||
frame.trailer = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return frame.trailer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (frame *hixieSentinelFrameReader) Len() int { return -1 }
|
|
||||||
|
|
||||||
// A HixieFrameReaderFactory creates new frame reader based on its frame type.
|
|
||||||
type hixieFrameReaderFactory struct {
|
|
||||||
*bufio.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (buf hixieFrameReaderFactory) NewFrameReader() (r frameReader, err error) {
|
|
||||||
var header []byte
|
|
||||||
var b byte
|
|
||||||
b, err = buf.ReadByte()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
header = append(header, b)
|
|
||||||
if b&0x80 == 0x80 {
|
|
||||||
length, lengthFields, err := readHixieLength(buf.Reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if length == 0 {
|
|
||||||
return nil, io.EOF
|
|
||||||
}
|
|
||||||
header = append(header, lengthFields...)
|
|
||||||
return &hixieLengthFrameReader{
|
|
||||||
reader: io.LimitReader(buf.Reader, length),
|
|
||||||
FrameType: b,
|
|
||||||
Length: length,
|
|
||||||
header: bytes.NewBuffer(header)}, err
|
|
||||||
}
|
|
||||||
return &hixieSentinelFrameReader{
|
|
||||||
reader: buf.Reader,
|
|
||||||
FrameType: b,
|
|
||||||
header: bytes.NewBuffer(header)}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type hixiFrameWriter struct {
|
|
||||||
writer *bufio.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (frame *hixiFrameWriter) Write(msg []byte) (n int, err error) {
|
|
||||||
frame.writer.WriteByte(0)
|
|
||||||
frame.writer.Write(msg)
|
|
||||||
frame.writer.WriteByte(0xff)
|
|
||||||
err = frame.writer.Flush()
|
|
||||||
return len(msg), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (frame *hixiFrameWriter) Close() error { return nil }
|
|
||||||
|
|
||||||
type hixiFrameWriterFactory struct {
|
|
||||||
*bufio.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (buf hixiFrameWriterFactory) NewFrameWriter(payloadType byte) (frame frameWriter, err error) {
|
|
||||||
if payloadType != TextFrame {
|
|
||||||
return nil, ErrNotSupported
|
|
||||||
}
|
|
||||||
return &hixiFrameWriter{writer: buf.Writer}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type hixiFrameHandler struct {
|
|
||||||
conn *Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (handler *hixiFrameHandler) HandleFrame(frame frameReader) (r frameReader, err error) {
|
|
||||||
if header := frame.HeaderReader(); header != nil {
|
|
||||||
io.Copy(ioutil.Discard, header)
|
|
||||||
}
|
|
||||||
if frame.PayloadType() != TextFrame {
|
|
||||||
io.Copy(ioutil.Discard, frame)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return frame, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (handler *hixiFrameHandler) WriteClose(_ int) (err error) {
|
|
||||||
handler.conn.wio.Lock()
|
|
||||||
defer handler.conn.wio.Unlock()
|
|
||||||
closingFrame := []byte{'\xff', '\x00'}
|
|
||||||
handler.conn.buf.Write(closingFrame)
|
|
||||||
return handler.conn.buf.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
// newHixiConn creates a new WebSocket connection speaking hixie draft protocol.
|
|
||||||
func newHixieConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
|
|
||||||
if buf == nil {
|
|
||||||
br := bufio.NewReader(rwc)
|
|
||||||
bw := bufio.NewWriter(rwc)
|
|
||||||
buf = bufio.NewReadWriter(br, bw)
|
|
||||||
}
|
|
||||||
ws := &Conn{config: config, request: request, buf: buf, rwc: rwc,
|
|
||||||
frameReaderFactory: hixieFrameReaderFactory{buf.Reader},
|
|
||||||
frameWriterFactory: hixiFrameWriterFactory{buf.Writer},
|
|
||||||
PayloadType: TextFrame}
|
|
||||||
ws.frameHandler = &hixiFrameHandler{ws}
|
|
||||||
return ws
|
|
||||||
}
|
|
||||||
|
|
||||||
// getChallengeResponse computes the expected response from the
|
|
||||||
// challenge as described in section 5.1 Opening Handshake steps 42 to
|
|
||||||
// 43 of http://www.whatwg.org/specs/web-socket-protocol/
|
|
||||||
func getChallengeResponse(number1, number2 uint32, key3 []byte) (expected []byte, err error) {
|
|
||||||
// 41. Let /challenge/ be the concatenation of /number_1/, expressed
|
|
||||||
// a big-endian 32 bit integer, /number_2/, expressed in a big-
|
|
||||||
// endian 32 bit integer, and the eight bytes of /key_3/ in the
|
|
||||||
// order they were sent to the wire.
|
|
||||||
challenge := make([]byte, 16)
|
|
||||||
binary.BigEndian.PutUint32(challenge[0:], number1)
|
|
||||||
binary.BigEndian.PutUint32(challenge[4:], number2)
|
|
||||||
copy(challenge[8:], key3)
|
|
||||||
|
|
||||||
// 42. Let /expected/ be the MD5 fingerprint of /challenge/ as a big-
|
|
||||||
// endian 128 bit string.
|
|
||||||
h := md5.New()
|
|
||||||
if _, err = h.Write(challenge); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
expected = h.Sum(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generates handshake key as described in 4.1 Opening handshake step 16 to 22.
|
|
||||||
// cf. http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
|
|
||||||
func generateKeyNumber() (key string, number uint32) {
|
|
||||||
// 16. Let /spaces_n/ be a random integer from 1 to 12 inclusive.
|
|
||||||
spaces := rand.Intn(12) + 1
|
|
||||||
|
|
||||||
// 17. Let /max_n/ be the largest integer not greater than
|
|
||||||
// 4,294,967,295 divided by /spaces_n/
|
|
||||||
max := int(4294967295 / uint32(spaces))
|
|
||||||
|
|
||||||
// 18. Let /number_n/ be a random integer from 0 to /max_n/ inclusive.
|
|
||||||
number = uint32(rand.Intn(max + 1))
|
|
||||||
|
|
||||||
// 19. Let /product_n/ be the result of multiplying /number_n/ and
|
|
||||||
// /spaces_n/ together.
|
|
||||||
product := number * uint32(spaces)
|
|
||||||
|
|
||||||
// 20. Let /key_n/ be a string consisting of /product_n/, expressed
|
|
||||||
// in base ten using the numerals in the range U+0030 DIGIT ZERO (0)
|
|
||||||
// to U+0039 DIGIT NINE (9).
|
|
||||||
key = fmt.Sprintf("%d", product)
|
|
||||||
|
|
||||||
// 21. Insert between one and twelve random characters from the ranges
|
|
||||||
// U+0021 to U+002F and U+003A to U+007E into /key_n/ at random
|
|
||||||
// positions.
|
|
||||||
n := rand.Intn(12) + 1
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
pos := rand.Intn(len(key)) + 1
|
|
||||||
ch := secKeyRandomChars[rand.Intn(len(secKeyRandomChars))]
|
|
||||||
key = key[0:pos] + string(ch) + key[pos:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 22. Insert /spaces_n/ U+0020 SPACE characters into /key_n/ at random
|
|
||||||
// positions other than the start or end of the string.
|
|
||||||
for i := 0; i < spaces; i++ {
|
|
||||||
pos := rand.Intn(len(key)-1) + 1
|
|
||||||
key = key[0:pos] + " " + key[pos:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generates handshake key_3 as described in 4.1 Opening handshake step 26.
|
|
||||||
// cf. http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
|
|
||||||
func generateKey3() (key []byte) {
|
|
||||||
// 26. Let /key3/ be a string consisting of eight random bytes (or
|
|
||||||
// equivalently, a random 64 bit integer encoded in big-endian order).
|
|
||||||
key = make([]byte, 8)
|
|
||||||
for i := 0; i < 8; i++ {
|
|
||||||
key[i] = byte(rand.Intn(256))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cilent handhake described in (soon obsolete)
|
|
||||||
// draft-ietf-hybi-thewebsocket-protocol-00
|
|
||||||
// (draft-hixie-thewebsocket-protocol-76)
|
|
||||||
func hixie76ClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) {
|
|
||||||
switch config.Version {
|
|
||||||
case ProtocolVersionHixie76, ProtocolVersionHybi00:
|
|
||||||
default:
|
|
||||||
panic("wrong protocol version.")
|
|
||||||
}
|
|
||||||
// 4.1. Opening handshake.
|
|
||||||
// Step 5. send a request line.
|
|
||||||
bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")
|
|
||||||
|
|
||||||
// Step 6-14. push request headers in fields.
|
|
||||||
fields := []string{
|
|
||||||
"Upgrade: WebSocket\r\n",
|
|
||||||
"Connection: Upgrade\r\n",
|
|
||||||
"Host: " + config.Location.Host + "\r\n",
|
|
||||||
"Origin: " + config.Origin.String() + "\r\n",
|
|
||||||
}
|
|
||||||
if len(config.Protocol) > 0 {
|
|
||||||
if len(config.Protocol) != 1 {
|
|
||||||
return ErrBadWebSocketProtocol
|
|
||||||
}
|
|
||||||
fields = append(fields, "Sec-WebSocket-Protocol: "+config.Protocol[0]+"\r\n")
|
|
||||||
}
|
|
||||||
// TODO(ukai): Step 15. send cookie if any.
|
|
||||||
|
|
||||||
// Step 16-23. generate keys and push Sec-WebSocket-Key<n> in fields.
|
|
||||||
key1, number1 := generateKeyNumber()
|
|
||||||
key2, number2 := generateKeyNumber()
|
|
||||||
if config.handshakeData != nil {
|
|
||||||
key1 = config.handshakeData["key1"]
|
|
||||||
n, err := strconv.ParseUint(config.handshakeData["number1"], 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
number1 = uint32(n)
|
|
||||||
key2 = config.handshakeData["key2"]
|
|
||||||
n, err = strconv.ParseUint(config.handshakeData["number2"], 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
number2 = uint32(n)
|
|
||||||
}
|
|
||||||
fields = append(fields, "Sec-WebSocket-Key1: "+key1+"\r\n")
|
|
||||||
fields = append(fields, "Sec-WebSocket-Key2: "+key2+"\r\n")
|
|
||||||
|
|
||||||
// Step 24. shuffle fields and send them out.
|
|
||||||
for i := 1; i < len(fields); i++ {
|
|
||||||
j := rand.Intn(i)
|
|
||||||
fields[i], fields[j] = fields[j], fields[i]
|
|
||||||
}
|
|
||||||
for i := 0; i < len(fields); i++ {
|
|
||||||
bw.WriteString(fields[i])
|
|
||||||
}
|
|
||||||
// Step 25. send CRLF.
|
|
||||||
bw.WriteString("\r\n")
|
|
||||||
|
|
||||||
// Step 26. generate 8 bytes random key.
|
|
||||||
key3 := generateKey3()
|
|
||||||
if config.handshakeData != nil {
|
|
||||||
key3 = []byte(config.handshakeData["key3"])
|
|
||||||
}
|
|
||||||
// Step 27. send it out.
|
|
||||||
bw.Write(key3)
|
|
||||||
if err = bw.Flush(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 28-29, 32-40. read response from server.
|
|
||||||
resp, err := http.ReadResponse(br, &http.Request{Method: "GET"})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Step 30. check response code is 101.
|
|
||||||
if resp.StatusCode != 101 {
|
|
||||||
return ErrBadStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 41. check websocket headers.
|
|
||||||
if resp.Header.Get("Upgrade") != "WebSocket" ||
|
|
||||||
strings.ToLower(resp.Header.Get("Connection")) != "upgrade" {
|
|
||||||
return ErrBadUpgrade
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.Header.Get("Sec-Websocket-Origin") != config.Origin.String() {
|
|
||||||
return ErrBadWebSocketOrigin
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.Header.Get("Sec-Websocket-Location") != config.Location.String() {
|
|
||||||
return ErrBadWebSocketLocation
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(config.Protocol) > 0 && resp.Header.Get("Sec-Websocket-Protocol") != config.Protocol[0] {
|
|
||||||
return ErrBadWebSocketProtocol
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 42-43. get expected data from challenge data.
|
|
||||||
expected, err := getChallengeResponse(number1, number2, key3)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 44. read 16 bytes from server.
|
|
||||||
reply := make([]byte, 16)
|
|
||||||
if _, err = io.ReadFull(br, reply); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 45. check the reply equals to expected data.
|
|
||||||
if !bytes.Equal(expected, reply) {
|
|
||||||
return ErrChallengeResponse
|
|
||||||
}
|
|
||||||
// WebSocket connection is established.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client Handshake described in (soon obsolete)
|
|
||||||
// draft-hixie-thewebsocket-protocol-75.
|
|
||||||
func hixie75ClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) {
|
|
||||||
if config.Version != ProtocolVersionHixie75 {
|
|
||||||
panic("wrong protocol version.")
|
|
||||||
}
|
|
||||||
bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")
|
|
||||||
bw.WriteString("Upgrade: WebSocket\r\n")
|
|
||||||
bw.WriteString("Connection: Upgrade\r\n")
|
|
||||||
bw.WriteString("Host: " + config.Location.Host + "\r\n")
|
|
||||||
bw.WriteString("Origin: " + config.Origin.String() + "\r\n")
|
|
||||||
if len(config.Protocol) > 0 {
|
|
||||||
if len(config.Protocol) != 1 {
|
|
||||||
return ErrBadWebSocketProtocol
|
|
||||||
}
|
|
||||||
bw.WriteString("WebSocket-Protocol: " + config.Protocol[0] + "\r\n")
|
|
||||||
}
|
|
||||||
bw.WriteString("\r\n")
|
|
||||||
bw.Flush()
|
|
||||||
resp, err := http.ReadResponse(br, &http.Request{Method: "GET"})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if resp.Status != "101 Web Socket Protocol Handshake" {
|
|
||||||
return ErrBadStatus
|
|
||||||
}
|
|
||||||
if resp.Header.Get("Upgrade") != "WebSocket" ||
|
|
||||||
resp.Header.Get("Connection") != "Upgrade" {
|
|
||||||
return ErrBadUpgrade
|
|
||||||
}
|
|
||||||
if resp.Header.Get("Websocket-Origin") != config.Origin.String() {
|
|
||||||
return ErrBadWebSocketOrigin
|
|
||||||
}
|
|
||||||
if resp.Header.Get("Websocket-Location") != config.Location.String() {
|
|
||||||
return ErrBadWebSocketLocation
|
|
||||||
}
|
|
||||||
if len(config.Protocol) > 0 && resp.Header.Get("Websocket-Protocol") != config.Protocol[0] {
|
|
||||||
return ErrBadWebSocketProtocol
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// newHixieClientConn returns new WebSocket connection speaking hixie draft protocol.
|
|
||||||
func newHixieClientConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser) *Conn {
|
|
||||||
return newHixieConn(config, buf, rwc, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets key number from Sec-WebSocket-Key<n>: field as described
|
|
||||||
// in 5.2 Sending the server's opening handshake, 4.
|
|
||||||
func getKeyNumber(s string) (r uint32) {
|
|
||||||
// 4. Let /key-number_n/ be the digits (characters in the range
|
|
||||||
// U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9)) in /key_1/,
|
|
||||||
// interpreted as a base ten integer, ignoring all other characters
|
|
||||||
// in /key_n/.
|
|
||||||
r = 0
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
if s[i] >= '0' && s[i] <= '9' {
|
|
||||||
r = r*10 + uint32(s[i]) - '0'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Hixie76ServerHandshaker performs a server handshake using
|
|
||||||
// hixie draft 76 protocol.
|
|
||||||
type hixie76ServerHandshaker struct {
|
|
||||||
*Config
|
|
||||||
challengeResponse []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *hixie76ServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) {
|
|
||||||
c.Version = ProtocolVersionHybi00
|
|
||||||
if req.Method != "GET" {
|
|
||||||
return http.StatusMethodNotAllowed, ErrBadRequestMethod
|
|
||||||
}
|
|
||||||
// HTTP version can be safely ignored.
|
|
||||||
|
|
||||||
if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" ||
|
|
||||||
strings.ToLower(req.Header.Get("Connection")) != "upgrade" {
|
|
||||||
return http.StatusBadRequest, ErrNotWebSocket
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(ukai): check Host
|
|
||||||
c.Origin, err = url.ParseRequestURI(req.Header.Get("Origin"))
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusBadRequest, err
|
|
||||||
}
|
|
||||||
|
|
||||||
key1 := req.Header.Get("Sec-Websocket-Key1")
|
|
||||||
if key1 == "" {
|
|
||||||
return http.StatusBadRequest, ErrChallengeResponse
|
|
||||||
}
|
|
||||||
key2 := req.Header.Get("Sec-Websocket-Key2")
|
|
||||||
if key2 == "" {
|
|
||||||
return http.StatusBadRequest, ErrChallengeResponse
|
|
||||||
}
|
|
||||||
key3 := make([]byte, 8)
|
|
||||||
if _, err := io.ReadFull(buf, key3); err != nil {
|
|
||||||
return http.StatusBadRequest, ErrChallengeResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
var scheme string
|
|
||||||
if req.TLS != nil {
|
|
||||||
scheme = "wss"
|
|
||||||
} else {
|
|
||||||
scheme = "ws"
|
|
||||||
}
|
|
||||||
c.Location, err = url.ParseRequestURI(scheme + "://" + req.Host + req.URL.RequestURI())
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusBadRequest, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 4. get key number in Sec-WebSocket-Key<n> fields.
|
|
||||||
keyNumber1 := getKeyNumber(key1)
|
|
||||||
keyNumber2 := getKeyNumber(key2)
|
|
||||||
|
|
||||||
// Step 5. get number of spaces in Sec-WebSocket-Key<n> fields.
|
|
||||||
space1 := uint32(strings.Count(key1, " "))
|
|
||||||
space2 := uint32(strings.Count(key2, " "))
|
|
||||||
if space1 == 0 || space2 == 0 {
|
|
||||||
return http.StatusBadRequest, ErrChallengeResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 6. key number must be an integral multiple of spaces.
|
|
||||||
if keyNumber1%space1 != 0 || keyNumber2%space2 != 0 {
|
|
||||||
return http.StatusBadRequest, ErrChallengeResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 7. let part be key number divided by spaces.
|
|
||||||
part1 := keyNumber1 / space1
|
|
||||||
part2 := keyNumber2 / space2
|
|
||||||
|
|
||||||
// Step 8. let challenge be concatenation of part1, part2 and key3.
|
|
||||||
// Step 9. get MD5 fingerprint of challenge.
|
|
||||||
c.challengeResponse, err = getChallengeResponse(part1, part2, key3)
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
protocol := strings.TrimSpace(req.Header.Get("Sec-Websocket-Protocol"))
|
|
||||||
protocols := strings.Split(protocol, ",")
|
|
||||||
for i := 0; i < len(protocols); i++ {
|
|
||||||
c.Protocol = append(c.Protocol, strings.TrimSpace(protocols[i]))
|
|
||||||
}
|
|
||||||
|
|
||||||
return http.StatusSwitchingProtocols, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *hixie76ServerHandshaker) AcceptHandshake(buf *bufio.Writer) (err error) {
|
|
||||||
if len(c.Protocol) > 0 {
|
|
||||||
if len(c.Protocol) != 1 {
|
|
||||||
return ErrBadWebSocketProtocol
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 10. send response status line.
|
|
||||||
buf.WriteString("HTTP/1.1 101 WebSocket Protocol Handshake\r\n")
|
|
||||||
// Step 11. send response headers.
|
|
||||||
buf.WriteString("Upgrade: WebSocket\r\n")
|
|
||||||
buf.WriteString("Connection: Upgrade\r\n")
|
|
||||||
buf.WriteString("Sec-WebSocket-Origin: " + c.Origin.String() + "\r\n")
|
|
||||||
buf.WriteString("Sec-WebSocket-Location: " + c.Location.String() + "\r\n")
|
|
||||||
if len(c.Protocol) > 0 {
|
|
||||||
buf.WriteString("Sec-WebSocket-Protocol: " + c.Protocol[0] + "\r\n")
|
|
||||||
}
|
|
||||||
// Step 12. send CRLF.
|
|
||||||
buf.WriteString("\r\n")
|
|
||||||
// Step 13. send response data.
|
|
||||||
buf.Write(c.challengeResponse)
|
|
||||||
return buf.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *hixie76ServerHandshaker) NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) (conn *Conn) {
|
|
||||||
return newHixieServerConn(c.Config, buf, rwc, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A hixie75ServerHandshaker performs a server handshake using
|
|
||||||
// hixie draft 75 protocol.
|
|
||||||
type hixie75ServerHandshaker struct {
|
|
||||||
*Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *hixie75ServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) {
|
|
||||||
c.Version = ProtocolVersionHixie75
|
|
||||||
if req.Method != "GET" || req.Proto != "HTTP/1.1" {
|
|
||||||
return http.StatusMethodNotAllowed, ErrBadRequestMethod
|
|
||||||
}
|
|
||||||
if req.Header.Get("Upgrade") != "WebSocket" {
|
|
||||||
return http.StatusBadRequest, ErrNotWebSocket
|
|
||||||
}
|
|
||||||
if req.Header.Get("Connection") != "Upgrade" {
|
|
||||||
return http.StatusBadRequest, ErrNotWebSocket
|
|
||||||
}
|
|
||||||
c.Origin, err = url.ParseRequestURI(strings.TrimSpace(req.Header.Get("Origin")))
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusBadRequest, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var scheme string
|
|
||||||
if req.TLS != nil {
|
|
||||||
scheme = "wss"
|
|
||||||
} else {
|
|
||||||
scheme = "ws"
|
|
||||||
}
|
|
||||||
c.Location, err = url.ParseRequestURI(scheme + "://" + req.Host + req.URL.RequestURI())
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusBadRequest, err
|
|
||||||
}
|
|
||||||
protocol := strings.TrimSpace(req.Header.Get("Websocket-Protocol"))
|
|
||||||
protocols := strings.Split(protocol, ",")
|
|
||||||
for i := 0; i < len(protocols); i++ {
|
|
||||||
c.Protocol = append(c.Protocol, strings.TrimSpace(protocols[i]))
|
|
||||||
}
|
|
||||||
|
|
||||||
return http.StatusSwitchingProtocols, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *hixie75ServerHandshaker) AcceptHandshake(buf *bufio.Writer) (err error) {
|
|
||||||
if len(c.Protocol) > 0 {
|
|
||||||
if len(c.Protocol) != 1 {
|
|
||||||
return ErrBadWebSocketProtocol
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteString("HTTP/1.1 101 Web Socket Protocol Handshake\r\n")
|
|
||||||
buf.WriteString("Upgrade: WebSocket\r\n")
|
|
||||||
buf.WriteString("Connection: Upgrade\r\n")
|
|
||||||
buf.WriteString("WebSocket-Origin: " + c.Origin.String() + "\r\n")
|
|
||||||
buf.WriteString("WebSocket-Location: " + c.Location.String() + "\r\n")
|
|
||||||
if len(c.Protocol) > 0 {
|
|
||||||
buf.WriteString("WebSocket-Protocol: " + c.Protocol[0] + "\r\n")
|
|
||||||
}
|
|
||||||
buf.WriteString("\r\n")
|
|
||||||
return buf.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *hixie75ServerHandshaker) NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) (conn *Conn) {
|
|
||||||
return newHixieServerConn(c.Config, buf, rwc, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
// newHixieServerConn returns a new WebSocket connection speaking hixie draft protocol.
|
|
||||||
func newHixieServerConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
|
|
||||||
return newHixieConn(config, buf, rwc, request)
|
|
||||||
}
|
|
@@ -1,201 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,549 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
@@ -1,584 +0,0 @@
|
|||||||
// 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())
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,102 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
@@ -1,412 +0,0 @@
|
|||||||
// 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}
|
|
@@ -1,274 +0,0 @@
|
|||||||
// 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()
|
|
||||||
}
|
|
@@ -1,29 +0,0 @@
|
|||||||
// 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)
|
|
||||||
|
|
||||||
}
|
|
@@ -1,146 +0,0 @@
|
|||||||
//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
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,24 +0,0 @@
|
|||||||
// 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))
|
|
||||||
}
|
|
@@ -1,23 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,17 +0,0 @@
|
|||||||
// 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.
|
|
||||||
*/
|
|
@@ -1,30 +0,0 @@
|
|||||||
// 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
|
|
||||||
*/
|
|
@@ -1,14 +0,0 @@
|
|||||||
// 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!
|
|
@@ -1,57 +0,0 @@
|
|||||||
// robust_webserver.go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
const form = `<html><body><form action="#" method="post" name="bar">
|
|
||||||
<input type="text" name="in"/>
|
|
||||||
<input type="submit" value="Submit"/>
|
|
||||||
</form></html></body>`
|
|
||||||
|
|
||||||
type HandleFnc func(http.ResponseWriter,*http.Request)
|
|
||||||
|
|
||||||
/* handle a simple get request */
|
|
||||||
func SimpleServer(w http.ResponseWriter, request *http.Request) {
|
|
||||||
io.WriteString(w, "<h1>hello, world</h1>")
|
|
||||||
}
|
|
||||||
|
|
||||||
/* handle a form, both the GET which displays the form
|
|
||||||
and the POST which processes it.*/
|
|
||||||
func FormServer(w http.ResponseWriter, request *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
|
||||||
switch request.Method {
|
|
||||||
case "GET":
|
|
||||||
/* display the form to the user */
|
|
||||||
io.WriteString(w, form );
|
|
||||||
case "POST":
|
|
||||||
/* handle the form data, note that ParseForm must
|
|
||||||
be called before we can extract form data*/
|
|
||||||
//request.ParseForm();
|
|
||||||
//io.WriteString(w, request.Form["in"][0])
|
|
||||||
io.WriteString(w, request.FormValue("in"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
http.HandleFunc("/test1", logPanics(SimpleServer))
|
|
||||||
http.HandleFunc("/test2", logPanics(FormServer))
|
|
||||||
if err := http.ListenAndServe(":8088", nil); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func logPanics(function HandleFnc) HandleFnc {
|
|
||||||
return func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
defer func() {
|
|
||||||
if x := recover(); x != nil {
|
|
||||||
log.Printf("[%v] caught panic: %v", request.RemoteAddr, x)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
function(writer, request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@@ -1,39 +0,0 @@
|
|||||||
// 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
|
|
||||||
*/
|
|
@@ -1,13 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
@@ -1,32 +0,0 @@
|
|||||||
// 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
|
|
||||||
*/
|
|
@@ -1,37 +0,0 @@
|
|||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,86 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,42 +0,0 @@
|
|||||||
// simple_webserver.go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
const form = `<html><body><form action="#" method="post" name="bar">
|
|
||||||
<input type="text" name="in"/>
|
|
||||||
<input type="submit" value="Submit"/>
|
|
||||||
</form></html></body>`
|
|
||||||
|
|
||||||
/* handle a simple get request */
|
|
||||||
func SimpleServer(w http.ResponseWriter, request *http.Request) {
|
|
||||||
io.WriteString(w, "<h1>hello, world</h1>")
|
|
||||||
}
|
|
||||||
|
|
||||||
/* handle a form, both the GET which displays the form
|
|
||||||
and the POST which processes it.*/
|
|
||||||
func FormServer(w http.ResponseWriter, request *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
|
||||||
switch request.Method {
|
|
||||||
case "GET":
|
|
||||||
/* display the form to the user */
|
|
||||||
io.WriteString(w, form );
|
|
||||||
case "POST":
|
|
||||||
/* handle the form data, note that ParseForm must
|
|
||||||
be called before we can extract form data*/
|
|
||||||
//request.ParseForm();
|
|
||||||
//io.WriteString(w, request.Form["in"][0])
|
|
||||||
io.WriteString(w, request.FormValue("in"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
http.HandleFunc("/test1", SimpleServer)
|
|
||||||
http.HandleFunc("/test2", FormServer)
|
|
||||||
if err := http.ListenAndServe(":8088", nil); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,30 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@@ -1,29 +0,0 @@
|
|||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,32 +0,0 @@
|
|||||||
// 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()
|
|
||||||
}
|
|
@@ -1,23 +0,0 @@
|
|||||||
// 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!
|
|
@@ -1,26 +0,0 @@
|
|||||||
// 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.
|
|
||||||
*/
|
|
@@ -1,22 +0,0 @@
|
|||||||
// 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
|
|
||||||
*/
|
|
@@ -1,24 +0,0 @@
|
|||||||
// 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!
|
|
||||||
*/
|
|
@@ -1,20 +0,0 @@
|
|||||||
// 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!
|
|
||||||
*/
|
|
@@ -1,42 +0,0 @@
|
|||||||
// twitter_status.go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"fmt"
|
|
||||||
"encoding/xml"
|
|
||||||
"io/ioutil"
|
|
||||||
)
|
|
||||||
/* these structs will house the unmarshalled response.
|
|
||||||
they should be hierarchically shaped like the XML
|
|
||||||
but can omit irrelevant data. */
|
|
||||||
type Status struct {
|
|
||||||
Text string
|
|
||||||
}
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
XMLName xml.Name
|
|
||||||
Status Status
|
|
||||||
}
|
|
||||||
// var user User
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// perform an HTTP request for the twitter status of user: Googland
|
|
||||||
resp, _ := http.Get("http://twitter.com/users/Googland.xml")
|
|
||||||
// initialize the structure of the XML response
|
|
||||||
user := User{xml.Name{"", "user"}, Status{""}}
|
|
||||||
// unmarshal the XML into our structures
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if body, err := ioutil.ReadAll(resp.Body); err != nil {
|
|
||||||
fmt.Printf("error: %s", err.Error())
|
|
||||||
} else {
|
|
||||||
fmt.Printf("%s ---", body)
|
|
||||||
xml.Unmarshal(body, &user)
|
|
||||||
}
|
|
||||||
fmt.Printf("name: %s ", user.XMLName)
|
|
||||||
fmt.Printf("status: %s", user.Status.Text)
|
|
||||||
}
|
|
||||||
/* Output:
|
|
||||||
status: Robot cars invade California, on orders from Google: Google has been testing self-driving cars ... http://bit.ly/cbtpUN http://retwt.me/97p<exit code="0" msg="process exited normally"/>
|
|
||||||
After Go1: no output: name: { user} status:
|
|
||||||
*/
|
|
@@ -1,29 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,29 +0,0 @@
|
|||||||
// 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())
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,2 +0,0 @@
|
|||||||
Testing input of new page!
|
|
||||||
Go Go Go !
|
|
@@ -1 +0,0 @@
|
|||||||
This is a sample Page.
|
|
@@ -1,6 +0,0 @@
|
|||||||
<h1>Editing {{.Title |html}}</h1>
|
|
||||||
|
|
||||||
<form action="/save/{{.Title |html}}" method="POST">
|
|
||||||
<div><textarea name="body" rows="20" cols="80">{{printf "%s" .Body|html}}</textarea></div>
|
|
||||||
<div><input type="submit" value="Save"></div>
|
|
||||||
</form>
|
|
@@ -1,2 +0,0 @@
|
|||||||
Hello Go - World !!!
|
|
||||||
This works great.
|
|
@@ -1 +0,0 @@
|
|||||||
This is a test!!
|
|
@@ -1,3 +0,0 @@
|
|||||||
Page5 is hereby started.
|
|
||||||
This is a first addition.
|
|
||||||
2nd addition.
|
|
@@ -1,5 +0,0 @@
|
|||||||
<h1>{{.Title |html}}</h1>
|
|
||||||
|
|
||||||
<p>[<a href="/edit/{{.Title |html}}">edit</a>]</p>
|
|
||||||
|
|
||||||
<div>{{printf "%s" .Body |html}}</div>
|
|
@@ -1,97 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@@ -1,32 +0,0 @@
|
|||||||
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))
|
|
||||||
}
|
|
@@ -1,39 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"io/ioutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Page struct {
|
|
||||||
Title string
|
|
||||||
Body []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Page) save() error {
|
|
||||||
filename := p.Title + ".txt"
|
|
||||||
return ioutil.WriteFile(filename, p.Body, 0600)
|
|
||||||
}
|
|
||||||
|
|
||||||
func load(title string) (*Page, error) {
|
|
||||||
filename := title + ".txt"
|
|
||||||
body, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Page{Title: title, Body: body}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const lenPath = len("/view/")
|
|
||||||
|
|
||||||
func viewHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
title := r.URL.Path[lenPath:]
|
|
||||||
p, _ := load(title)
|
|
||||||
fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", p.Title, p.Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
http.HandleFunc("/view/", viewHandler)
|
|
||||||
http.ListenAndServe(":8080", nil)
|
|
||||||
}
|
|
12
eBook/examples/chapter_5/booleans.go
Normal file
12
eBook/examples/chapter_5/booleans.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
bool1 := true
|
||||||
|
if bool1 {
|
||||||
|
fmt.Printf("The value is true\n")
|
||||||
|
} else {
|
||||||
|
fmt.Printf("The value is false\n")
|
||||||
|
}
|
||||||
|
}
|
9
eBook/examples/chapter_5/for1.go
Normal file
9
eBook/examples/chapter_5/for1.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
fmt.Printf("This is the %d iteration\n", i)
|
||||||
|
}
|
||||||
|
}
|
12
eBook/examples/chapter_5/for2.go
Normal file
12
eBook/examples/chapter_5/for2.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var i int = 5
|
||||||
|
|
||||||
|
for i >= 0 {
|
||||||
|
i = i - 1
|
||||||
|
fmt.Printf("The variable i is now: %d\n", i)
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user