SR Technology WTK Repo
Check-in [02630ca04a]
Not logged in
Bounty program for improvements to Tcl and certain Tcl packages.
Tcl 2019 Conference, Houston/TX, US, Nov 4-8
Send your abstracts to [email protected]
or submit via the online form by Sep 9.

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:decouple connection code so that WebSockets work without first requiring initial AJAX connection
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 02630ca04a29f520123fe08ef251e7284ae9566b
User & Date: stever 2013-01-18 16:01:13
Context
2013-01-18
19:25
Added stubs for most commands. check-in: e2ff912839 user: gerald tags: trunk
16:01
decouple connection code so that WebSockets work without first requiring initial AJAX connection check-in: 02630ca04a user: stever tags: trunk
15:30
reformat demo to reflect the concept of rendering the page view for MVC style apps check-in: ea0ef51845 user: stever tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to server.tcl.

83
84
85
86
87
88
89
90
91

92
93








94
95
96





97
98
99
100
101
102
103
...
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
...
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160



161
162
163
164
165
166
167
		append upgrade	"WebSocket-Location: ws://localhost:9001/wsctrl\r\n"
		append upgrade "Sec-WebSocket-Accept: $acceptKey"
		append upgrade "\r\n\r\n"
		fconfigure $sock -translation binary
		puts -nonewline $sock $upgrade
		flush $sock
		fileevent $sock readable [list ws_receive junk $sock]
		puts "Socket $sock upgraded to WebSocket"
		set sessionid [lindex [split [dict get $data query] =] end]

		dict set ::session($sessionid) wsock $sock
		dict set ::sock($sock) sessionid $sessionid








		ws_send $sock "Hello From Server"
		#disable bg polling loop 
		catch {after cancel $::cancel($sock)}





		return 1
	} else {
		#puts "\nVersion != 13 no good"
		close $sock
		return 0
	}
}
................................................................................
		httpd loadrequest $sock data query
		if {![info exists data(url)]} {return}
		regsub {(^http://[^/]+)?} $data(url) {} url
		puts stderr "URL: $url"
		set url [string trimleft $url /]
		switch -glob -- $url {
			""             {httpd return $sock [filecontents index.html]}
			"*.tcl"        {httpd return $sock [newSession $sock [string trimleft $url /] lib/wtkcoreapp.html]}
			"*.js"         {httpd return $sock [filecontents $url] -mimetype "text/javascript"}
			"*.gif"        {httpd returnfile $sock $url $url  "image/gif" [clock seconds] 1 -static }
			"*.png"        {httpd returnfile $sock $url $url  "image/png" [clock seconds] 1 -static }
			"*.jpg"        {httpd returnfile $sock $url $url  "image/jpg" [clock seconds] 1 -static }
			"*.ico"        {httpd returnfile $sock $url $url  "image/x-icon" [clock seconds] 1 -static }
			"wtkpoll.html" {if !{[sendany $sock $query(sessionid)]} {error "pending"}}
			"wtkcb.html"   {fromclient $query(sessionid) $query(cmd)}
................................................................................
# This is called when a client first loads one of our 'application' pages.  We create a new
# application instance (interpreter), load and initialize "wtk" in that interpreter, and then
# load in the Tcl script for the application we're running.  We return a HTML page that will
# load up the client side of wtk and cause the browser to initiate a connection back to the
# server. Notably, this page includes the 'sessionid' we've generated for the application
# instance, which is unique to each client.

proc newSession {sock script webpage} {
	set sessionid [incr ::sessioncounter]
	set interp [interp create]
	dict set ::session($sessionid) interp $interp
	dict set ::session($sessionid) msgq ""
	dict set ::session($sessionid) sock $sock
	dict set ::session($sessionid) wsock 0
	if {[catch {$interp eval source lib/wtk-base.tcl}]!=0} {puts $::errorInfo}
	$interp alias sendto toclient $sessionid
	$interp eval wtk::init sendto



	if {[catch {$interp eval source $script}]!=0} {puts $::errorInfo}
	if {[file exists favicon.ico]} {
		set link "<link href='data:image/x-icon;base64,%%%BASE64ICO%%%' rel='icon' type='image/x-icon' />"
		set favicon [string map "%%%BASE64ICO%%% [binary encode base64 [filecontents favicon.ico]]" $link]
	} else {
		set favicon ""
	}






<

>


>
>
>
>
>
>
>
>
|
<
<
>
>
>
>
>







 







|







 







|









>
>
>







83
84
85
86
87
88
89

90
91
92
93
94
95
96
97
98
99
100
101
102


103
104
105
106
107
108
109
110
111
112
113
114
...
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
...
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
		append upgrade	"WebSocket-Location: ws://localhost:9001/wsctrl\r\n"
		append upgrade "Sec-WebSocket-Accept: $acceptKey"
		append upgrade "\r\n\r\n"
		fconfigure $sock -translation binary
		puts -nonewline $sock $upgrade
		flush $sock
		fileevent $sock readable [list ws_receive junk $sock]

		set sessionid [lindex [split [dict get $data query] =] end]
		puts "Socket $sock upgraded to WebSocket for sessionid $sessionid"	
		dict set ::session($sessionid) wsock $sock
		dict set ::sock($sock) sessionid $sessionid

		#use this after full message frames are implemented
		#toclient $sessionid [dict get $::session($sessionid) msgq] 
		
		#for now I dish out small chucks of js to fit the 126 byte message length
		foreach message [split [string range [dict get $::session($sessionid) msgq] 0 end-1] \;] {
			#toclient $sessionid ${message};
			puts "WSSERVERA: ${message};"
			ws_send $sock "${message};"


			update idletasks
		}
		
		dict set ::session($sessionid) msgq ""
		
		return 1
	} else {
		#puts "\nVersion != 13 no good"
		close $sock
		return 0
	}
}
................................................................................
		httpd loadrequest $sock data query
		if {![info exists data(url)]} {return}
		regsub {(^http://[^/]+)?} $data(url) {} url
		puts stderr "URL: $url"
		set url [string trimleft $url /]
		switch -glob -- $url {
			""             {httpd return $sock [filecontents index.html]}
			"*.tcl"        {httpd return $sock [newSession $sock [string trimleft $url /] lib/wtkcoreapp.html [array get data]]}
			"*.js"         {httpd return $sock [filecontents $url] -mimetype "text/javascript"}
			"*.gif"        {httpd returnfile $sock $url $url  "image/gif" [clock seconds] 1 -static }
			"*.png"        {httpd returnfile $sock $url $url  "image/png" [clock seconds] 1 -static }
			"*.jpg"        {httpd returnfile $sock $url $url  "image/jpg" [clock seconds] 1 -static }
			"*.ico"        {httpd returnfile $sock $url $url  "image/x-icon" [clock seconds] 1 -static }
			"wtkpoll.html" {if !{[sendany $sock $query(sessionid)]} {error "pending"}}
			"wtkcb.html"   {fromclient $query(sessionid) $query(cmd)}
................................................................................
# This is called when a client first loads one of our 'application' pages.  We create a new
# application instance (interpreter), load and initialize "wtk" in that interpreter, and then
# load in the Tcl script for the application we're running.  We return a HTML page that will
# load up the client side of wtk and cause the browser to initiate a connection back to the
# server. Notably, this page includes the 'sessionid' we've generated for the application
# instance, which is unique to each client.

proc newSession {sock script webpage data} {
	set sessionid [incr ::sessioncounter]
	set interp [interp create]
	dict set ::session($sessionid) interp $interp
	dict set ::session($sessionid) msgq ""
	dict set ::session($sessionid) sock $sock
	dict set ::session($sessionid) wsock 0
	if {[catch {$interp eval source lib/wtk-base.tcl}]!=0} {puts $::errorInfo}
	$interp alias sendto toclient $sessionid
	$interp eval wtk::init sendto
	#pass in the server header vars first
	$interp eval [list set ::reqdata $data]
	#now source the app script
	if {[catch {$interp eval source $script}]!=0} {puts $::errorInfo}
	if {[file exists favicon.ico]} {
		set link "<link href='data:image/x-icon;base64,%%%BASE64ICO%%%' rel='icon' type='image/x-icon' />"
		set favicon [string map "%%%BASE64ICO%%% [binary encode base64 [filecontents favicon.ico]]" $link]
	} else {
		set favicon ""
	}

Changes to widgets/wtk.js.

1
2
3
4
5




6
7
8
9
10
11
12
..
30
31
32
33
34
35
36
37


38
39
40
41
42
43
44

45



46
47
48
49
50
51
52
53
54

55
56
57
58
59
60
61
62
63
64
65
66
67

68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
var wtk = {
    
    widgets : new Array(),
    objs : new Array(),
    




  
    /*
     *   Initialize, and manage two AJAX connections to the server; one is used to send
     *   messages, and the other polling connection is used to receive messages.  These
     *   correspond to the routines in server.tcl, and could be equally well replaced by
     *   a different and/or more reliable communications channel.
     */
................................................................................
					ws_uri += "/wsctrl";
					ws_uri += "?sessionid=" + sessionid;
					
					wtk.connection = new WebSocket(ws_uri);

					wtk.connection.onopen = function(){
				 		/*Send a small message to the console once the connection is established */
				 		console.log('Connection open!');


					};

					wtk.connection.onclose = function(){
				 		console.log('Connection closed');
						if (wtk.conntype != "websocket") {
							setTimeout(wtk.poller,100);
						} else {

							wtk.init();



						};
					};

					wtk.connection.onerror = function(error){
						console.log('Error detected: ' + error);
					};

					wtk.connection.onmessage = function(e){
						var server_message = e.data;

						eval(server_message);
						wtk.conntype = "websocket";
					};

					$.ajax({type:'GET', url:'wtkpoll.html?sessionid='+sessionid, dataType:'script', 
                                complete: function() {console.log("ws_init");},
                                error: function(jqXHR, textStatus, errorThrown) {
																	alert("Websocket connection interrupted\nPress OK to reconnect.");
                                  console.log('ajax error '+textStatus+' '+errorThrown);
                                  setTimeout(location.reload(1),200);}
								 });
				} else {
			 		/*WebSockets are not supported. Fallback to long-polling */

					setTimeout(wtk.poller,100);
				};


    },



    poller : function() {$.ajax({type:'GET', url:'wtkpoll.html?sessionid='+wtk.sessionid, dataType:'script', 
                                complete: function() {setTimeout(wtk.poller,100);},
                                error: function(jqXHR, textStatus, errorThrown) {
																	alert("Server connection interrupted\nPress OK to reconnect.");
																	console.log('ajax error '+textStatus+' '+errorThrown);
																	setTimeout(location.reload(1),200);}
                               });
												 },

    sendto : function(msg) { 
      if (wtk.conntype != "websocket") {
				$.get('wtkcb.html?sessionid='+wtk.sessionid, {cmd : msg});



|
>
>
>
>







 







|
>
>



|
|
|
|
>

>
>
>




|




>

<

<
<
<
<
<
<
<
<


>











|
|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
..
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

67








68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
var wtk = {
    
    widgets : new Array(),
    objs : new Array(),

		pause : function(ms) {
			ms += new Date().getTime();
			while (new Date() < ms){}
		},
  
    /*
     *   Initialize, and manage two AJAX connections to the server; one is used to send
     *   messages, and the other polling connection is used to receive messages.  These
     *   correspond to the routines in server.tcl, and could be equally well replaced by
     *   a different and/or more reliable communications channel.
     */
................................................................................
					ws_uri += "/wsctrl";
					ws_uri += "?sessionid=" + sessionid;
					
					wtk.connection = new WebSocket(ws_uri);

					wtk.connection.onopen = function(){
				 		/*Send a small message to the console once the connection is established */
				 		console.log('WebSocket connection open');
						wtk.conntype = "websocket";
						wtk.wsfailcnt = 0;
					};

					wtk.connection.onclose = function(){
				 		console.log('WebSocket connection closed');
						if (wtk.conntype = "websocket" && wtk.wsfailcnt <= 4) {
							console.log('WebSocket connection retry '+wtk.wsfailcnt+' of 4');
							wtk.pause(1000);
							wtk.wsfailcnt = wtk.wsfailcnt + 1;
							wtk.init();
						} else {
							console.log('Initiating AJAX fallback connection');
							setTimeout(wtk.poller,100);
						};
					};

					wtk.connection.onerror = function(error){
						console.log('WebSocket Error detected: ' + error);
					};

					wtk.connection.onmessage = function(e){
						var server_message = e.data;
						//console.log(server_message);
						eval(server_message);

					};








				} else {
			 		/*WebSockets are not supported. Fallback to long-polling */
					console.log('Browser doesnt support WebSockets, using AJAX instead');
					setTimeout(wtk.poller,100);
				};


    },



    poller : function() {$.ajax({type:'GET', url:'wtkpoll.html?sessionid='+wtk.sessionid, dataType:'script', 
                                complete: function() {setTimeout(wtk.poller,100);},
                                error: function(jqXHR, textStatus, errorThrown) {
																	//alert("AJAX server connection interrupted\nPress OK to reconnect.");
																	console.log('AJAX server connecton interrupted '+textStatus+' '+errorThrown);
																	setTimeout(location.reload(1),200);}
                               });
												 },

    sendto : function(msg) { 
      if (wtk.conntype != "websocket") {
				$.get('wtkcb.html?sessionid='+wtk.sessionid, {cmd : msg});