[1251] | 1 | # ---------------------------------------------------------------------- |
---|
| 2 | # LIBRARY: Handler |
---|
| 3 | # Base class for Client and Server. Handles protocol declarations |
---|
| 4 | # for messages received by the handler, and knows how to process |
---|
| 5 | # messages. |
---|
| 6 | # ---------------------------------------------------------------------- |
---|
| 7 | # Michael McLennan (mmclennan@purdue.edu) |
---|
| 8 | # ====================================================================== |
---|
[3177] | 9 | # Copyright (c) 2004-2012 HUBzero Foundation, LLC |
---|
[1257] | 10 | # |
---|
| 11 | # See the file "license.terms" for information on usage and |
---|
| 12 | # redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES. |
---|
[1251] | 13 | # ====================================================================== |
---|
| 14 | package require Itcl |
---|
| 15 | |
---|
| 16 | itcl::class Handler { |
---|
| 17 | private variable _namesp ;# maps protocol name => namespace of cmds |
---|
| 18 | private variable _parser ;# maps protocol name => parser for cmds |
---|
| 19 | private variable _buffer ;# maps connection => cmd buffer |
---|
| 20 | private variable _protocol ;# maps connection => protocol name |
---|
| 21 | private variable _cname ;# maps connection => nice name for debug |
---|
| 22 | private common _counter 0 |
---|
| 23 | |
---|
| 24 | constructor {args} { |
---|
| 25 | # |
---|
| 26 | # Define the DEFAULT protocol, which clients/servers use when |
---|
| 27 | # they first connect to define the protocol they intend to speak. |
---|
| 28 | # |
---|
| 29 | protocol DEFAULT |
---|
| 30 | define DEFAULT protocol {version} { |
---|
| 31 | variable handler |
---|
| 32 | variable cid |
---|
| 33 | $handler connectionSpeaks $cid $version |
---|
| 34 | return "" |
---|
| 35 | } |
---|
[1273] | 36 | define DEFAULT exception {message} { |
---|
| 37 | log error "ERROR: $message" |
---|
| 38 | } |
---|
[1251] | 39 | |
---|
| 40 | eval configure $args |
---|
| 41 | } |
---|
| 42 | |
---|
| 43 | destructor { |
---|
| 44 | foreach protocol [array names _parser] { |
---|
| 45 | interp delete $_parser($protocol) |
---|
| 46 | } |
---|
| 47 | foreach protocol [array names _namesp] { |
---|
| 48 | namespace delete $_namesp($protocol) |
---|
| 49 | } |
---|
| 50 | } |
---|
| 51 | |
---|
| 52 | public method protocol {name} |
---|
| 53 | public method define {protocol name arglist body} |
---|
[1273] | 54 | public method connections {{protocol *}} |
---|
[1251] | 55 | public method connectionName {cid {name ""}} |
---|
| 56 | public method connectionSpeaks {cid protocol} |
---|
| 57 | |
---|
| 58 | protected method handle {cid} |
---|
| 59 | protected method finalize {protocol} |
---|
[1273] | 60 | protected method dropped {cid} |
---|
[1251] | 61 | } |
---|
| 62 | |
---|
| 63 | # ---------------------------------------------------------------------- |
---|
| 64 | # USAGE: protocol <name> |
---|
| 65 | # |
---|
| 66 | # Used to define a protocol that this client/server recognizes. |
---|
| 67 | # A protocol has an associated safe interpreter full of commands |
---|
| 68 | # that the client/server recognizes. When each connection is |
---|
| 69 | # established, the other party must declare the protocol that it |
---|
| 70 | # intends to speak up front, so the client/server can select the |
---|
| 71 | # appropriate interpreter for all incoming requests. |
---|
| 72 | # ---------------------------------------------------------------------- |
---|
| 73 | itcl::body Handler::protocol {name} { |
---|
| 74 | if {[info exists _namesp($name)]} { |
---|
| 75 | error "protocol \"$name\" already defined" |
---|
| 76 | } |
---|
| 77 | set _namesp($name) "[namespace current]::bodies[incr _counter]" |
---|
| 78 | namespace eval $_namesp($name) {} |
---|
| 79 | set _parser($name) [interp create -safe] |
---|
| 80 | foreach cmd [$_parser($name) eval {info commands}] { |
---|
| 81 | $_parser($name) hide $cmd |
---|
| 82 | } |
---|
| 83 | $_parser($name) invokehidden proc unknown {args} {} |
---|
| 84 | $_parser($name) expose error |
---|
| 85 | } |
---|
| 86 | |
---|
| 87 | # ---------------------------------------------------------------------- |
---|
| 88 | # USAGE: define <protocol> <name> <arglist> <body> |
---|
| 89 | # |
---|
| 90 | # Used to define a command that this handler recognizes. The command |
---|
| 91 | # is called <name> in the safe interpreter associated with the given |
---|
| 92 | # <protocol>, which should have been defined previously via the |
---|
| 93 | # "protocol" method. The new command exists with the same name in a |
---|
| 94 | # special namespace in the main interpreter. It is implemented with |
---|
| 95 | # an argument list <arglist> and a <body> of Tcl code. |
---|
| 96 | # ---------------------------------------------------------------------- |
---|
| 97 | itcl::body Handler::define {protocol name arglist body} { |
---|
| 98 | if {![info exists _namesp($protocol)]} { |
---|
| 99 | error "can't define command \"$name\": protocol \"$protocol\" doesn't exist" |
---|
| 100 | } |
---|
| 101 | proc [set _namesp($protocol)]::$name $arglist $body |
---|
| 102 | $_parser($protocol) alias $name [set _namesp($protocol)]::$name |
---|
| 103 | finalize $protocol |
---|
| 104 | } |
---|
| 105 | |
---|
| 106 | # ---------------------------------------------------------------------- |
---|
[1273] | 107 | # USAGE: connections ?<protocol>? |
---|
| 108 | # |
---|
| 109 | # Returns a list of file handles for current connections that match |
---|
| 110 | # the glob-style <protocol> pattern. If no pattern is given, then |
---|
| 111 | # it returns all connections. Each handle can be passed to |
---|
| 112 | # connectionName and connectionSpeaks to get more information. |
---|
| 113 | # ---------------------------------------------------------------------- |
---|
| 114 | itcl::body Handler::connections {{pattern *}} { |
---|
| 115 | set rlist "" |
---|
| 116 | foreach cid [array names _protocol] { |
---|
| 117 | if {[string match $pattern $_protocol($cid)]} { |
---|
| 118 | lappend rlist $cid |
---|
| 119 | } |
---|
| 120 | } |
---|
| 121 | return $rlist |
---|
| 122 | } |
---|
| 123 | |
---|
| 124 | # ---------------------------------------------------------------------- |
---|
[1251] | 125 | # USAGE: connectionName <sockId> ?<name>? |
---|
| 126 | # |
---|
| 127 | # Used to set/get the nice name associated with a <sockId> connection. |
---|
| 128 | # The nice name is used for log messages, and makes debugging easier |
---|
| 129 | # than chasing around a bunch of "sock3" handle names. If no name |
---|
| 130 | # is specified, then it defaults to the file descriptor name. |
---|
| 131 | # ---------------------------------------------------------------------- |
---|
| 132 | itcl::body Handler::connectionName {cid {name ""}} { |
---|
| 133 | if {[string length $name] > 0} { |
---|
| 134 | set _cname($cid) $name |
---|
| 135 | } |
---|
| 136 | if {[info exists _cname($cid)]} { |
---|
| 137 | return "$_cname($cid) ($cid)" |
---|
| 138 | } |
---|
| 139 | return $cid |
---|
| 140 | } |
---|
| 141 | |
---|
| 142 | # ---------------------------------------------------------------------- |
---|
| 143 | # USAGE: connectionSpeaks <sockId> <protocol> |
---|
| 144 | # |
---|
| 145 | # Used internally to define what protocol the entity on the other |
---|
| 146 | # side of the connection speaks. This is usually invoked when that |
---|
| 147 | # entity sends the "protocol" message, and the built-in "protocol" |
---|
| 148 | # command in the DEFAULT parser uses this method to register the |
---|
| 149 | # protocol for the entity. |
---|
| 150 | # ---------------------------------------------------------------------- |
---|
| 151 | itcl::body Handler::connectionSpeaks {cid protocol} { |
---|
| 152 | if {"DEFAULT" != $protocol && ![info exists _parser($protocol)]} { |
---|
| 153 | error "protocol \"$protocol\" not recognized" |
---|
| 154 | } |
---|
| 155 | set _protocol($cid) $protocol |
---|
| 156 | } |
---|
| 157 | |
---|
| 158 | # ---------------------------------------------------------------------- |
---|
| 159 | # USAGE: handle <cid> |
---|
| 160 | # |
---|
| 161 | # Invoked automatically whenever a message comes in on the file |
---|
| 162 | # handle <cid> from the entity on the other side of the connection. |
---|
| 163 | # This handler reads the message and executes it in a safe |
---|
| 164 | # interpreter, thereby responding to it. |
---|
| 165 | # ---------------------------------------------------------------------- |
---|
| 166 | itcl::body Handler::handle {cid} { |
---|
| 167 | if {[gets $cid request] < 0} { |
---|
[1273] | 168 | dropped $cid |
---|
[1251] | 169 | } elseif {[info exists _protocol($cid)]} { |
---|
| 170 | # complete command? then process it below... |
---|
| 171 | append _buffer($cid) $request "\n" |
---|
| 172 | if {[info complete $_buffer($cid)]} { |
---|
| 173 | set request $_buffer($cid) |
---|
| 174 | set _buffer($cid) "" |
---|
| 175 | |
---|
| 176 | # what protocol does this entity speak? |
---|
| 177 | set protocol $_protocol($cid) |
---|
| 178 | |
---|
| 179 | # Some commands need to know the identity of the entity |
---|
| 180 | # on the other side of the connection. Save it as a |
---|
| 181 | # global variable in the namespace where the protocol |
---|
| 182 | # command exists. |
---|
| 183 | set [set _namesp($protocol)]::handler $this |
---|
| 184 | set [set _namesp($protocol)]::cid $cid |
---|
| 185 | |
---|
| 186 | # execute the incoming command... |
---|
| 187 | set mesg " => " |
---|
| 188 | if {[catch {$_parser($protocol) eval $request} result] == 0} { |
---|
| 189 | if {[string length $result] > 0} { |
---|
[1273] | 190 | catch {puts $cid $result} |
---|
[1251] | 191 | append mesg "ok: $result" |
---|
| 192 | } |
---|
| 193 | } else { |
---|
[1273] | 194 | catch {puts $cid [list exception $result]} |
---|
[1251] | 195 | append mesg "exception: $result" |
---|
| 196 | } |
---|
[2080] | 197 | log debug "incoming message from [connectionName $cid]: [string trimright $request \n] $mesg" |
---|
[1251] | 198 | } |
---|
| 199 | } |
---|
| 200 | } |
---|
| 201 | |
---|
| 202 | # ---------------------------------------------------------------------- |
---|
| 203 | # USAGE: finalize <protocol> |
---|
| 204 | # |
---|
| 205 | # Called whenever a new command is added to the handler. Updates |
---|
| 206 | # the "unknown" command to report a proper usage message (including |
---|
| 207 | # all valid keywords) when a bad command is encountered. |
---|
| 208 | # ---------------------------------------------------------------------- |
---|
| 209 | itcl::body Handler::finalize {protocol} { |
---|
| 210 | set p $_parser($protocol) |
---|
| 211 | $p hide error |
---|
| 212 | $p hide unknown |
---|
| 213 | set cmds [lsort [$p invokehidden info commands]] |
---|
| 214 | $p expose error |
---|
| 215 | $p expose unknown |
---|
| 216 | |
---|
| 217 | $p invokehidden proc unknown {cmd args} [format { |
---|
| 218 | error "bad command \"$cmd\": should be %s" |
---|
| 219 | } [join $cmds {, }]] |
---|
| 220 | } |
---|
| 221 | |
---|
| 222 | # ---------------------------------------------------------------------- |
---|
[1273] | 223 | # USAGE: dropped <cid> |
---|
| 224 | # |
---|
| 225 | # Invoked automatically whenever a client connection drops, to |
---|
| 226 | # log the event and remove all trace of the client. Derived classes |
---|
| 227 | # can override this method to perform other functions too. |
---|
| 228 | # ---------------------------------------------------------------------- |
---|
| 229 | itcl::body Handler::dropped {cid} { |
---|
| 230 | log system "dropped: [connectionName $cid]" |
---|
| 231 | |
---|
| 232 | # connection has connection -- forget this entity |
---|
| 233 | catch {close $cid} |
---|
| 234 | catch {unset _buffer($cid)} |
---|
| 235 | catch {unset _protocol($cid)} |
---|
| 236 | catch {unset _cname($cid)} |
---|
| 237 | } |
---|