redis.tcl 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. # Tcl client library - used by the Redis test
  2. # Copyright (C) 2009-2014 Salvatore Sanfilippo
  3. # Released under the BSD license like Redis itself
  4. #
  5. # Example usage:
  6. #
  7. # set r [redis 127.0.0.1 6379]
  8. # $r lpush mylist foo
  9. # $r lpush mylist bar
  10. # $r lrange mylist 0 -1
  11. # $r close
  12. #
  13. # Non blocking usage example:
  14. #
  15. # proc handlePong {r type reply} {
  16. # puts "PONG $type '$reply'"
  17. # if {$reply ne "PONG"} {
  18. # $r ping [list handlePong]
  19. # }
  20. # }
  21. #
  22. # set r [redis]
  23. # $r blocking 0
  24. # $r get fo [list handlePong]
  25. #
  26. # vwait forever
  27. package require Tcl 8.5
  28. package provide redis 0.1
  29. namespace eval redis {}
  30. set ::redis::id 0
  31. array set ::redis::fd {}
  32. array set ::redis::blocking {}
  33. array set ::redis::deferred {}
  34. array set ::redis::callback {}
  35. array set ::redis::state {} ;# State in non-blocking reply reading
  36. array set ::redis::statestack {} ;# Stack of states, for nested mbulks
  37. proc redis {{server 127.0.0.1} {port 6379} {defer 0}} {
  38. set fd [socket $server $port]
  39. fconfigure $fd -translation binary
  40. set id [incr ::redis::id]
  41. set ::redis::fd($id) $fd
  42. set ::redis::blocking($id) 1
  43. set ::redis::deferred($id) $defer
  44. ::redis::redis_reset_state $id
  45. interp alias {} ::redis::redisHandle$id {} ::redis::__dispatch__ $id
  46. }
  47. proc ::redis::__dispatch__ {id method args} {
  48. set fd $::redis::fd($id)
  49. set blocking $::redis::blocking($id)
  50. set deferred $::redis::deferred($id)
  51. if {$blocking == 0} {
  52. if {[llength $args] == 0} {
  53. error "Please provide a callback in non-blocking mode"
  54. }
  55. set callback [lindex $args end]
  56. set args [lrange $args 0 end-1]
  57. }
  58. if {[info command ::redis::__method__$method] eq {}} {
  59. set cmd "*[expr {[llength $args]+1}]\r\n"
  60. append cmd "$[string length $method]\r\n$method\r\n"
  61. foreach a $args {
  62. append cmd "$[string length $a]\r\n$a\r\n"
  63. }
  64. ::redis::redis_write $fd $cmd
  65. flush $fd
  66. if {!$deferred} {
  67. if {$blocking} {
  68. ::redis::redis_read_reply $fd
  69. } else {
  70. # Every well formed reply read will pop an element from this
  71. # list and use it as a callback. So pipelining is supported
  72. # in non blocking mode.
  73. lappend ::redis::callback($id) $callback
  74. fileevent $fd readable [list ::redis::redis_readable $fd $id]
  75. }
  76. }
  77. } else {
  78. uplevel 1 [list ::redis::__method__$method $id $fd] $args
  79. }
  80. }
  81. proc ::redis::__method__blocking {id fd val} {
  82. set ::redis::blocking($id) $val
  83. fconfigure $fd -blocking $val
  84. }
  85. proc ::redis::__method__read {id fd} {
  86. ::redis::redis_read_reply $fd
  87. }
  88. proc ::redis::__method__write {id fd buf} {
  89. ::redis::redis_write $fd $buf
  90. }
  91. proc ::redis::__method__flush {id fd} {
  92. flush $fd
  93. }
  94. proc ::redis::__method__close {id fd} {
  95. catch {close $fd}
  96. catch {unset ::redis::fd($id)}
  97. catch {unset ::redis::blocking($id)}
  98. catch {unset ::redis::state($id)}
  99. catch {unset ::redis::statestack($id)}
  100. catch {unset ::redis::callback($id)}
  101. catch {interp alias {} ::redis::redisHandle$id {}}
  102. }
  103. proc ::redis::__method__channel {id fd} {
  104. return $fd
  105. }
  106. proc ::redis::__method__deferred {id fd val} {
  107. set ::redis::deferred($id) $val
  108. }
  109. proc ::redis::redis_write {fd buf} {
  110. puts -nonewline $fd $buf
  111. }
  112. proc ::redis::redis_writenl {fd buf} {
  113. redis_write $fd $buf
  114. redis_write $fd "\r\n"
  115. flush $fd
  116. }
  117. proc ::redis::redis_readnl {fd len} {
  118. set buf [read $fd $len]
  119. read $fd 2 ; # discard CR LF
  120. return $buf
  121. }
  122. proc ::redis::redis_bulk_read {fd} {
  123. set count [redis_read_line $fd]
  124. if {$count == -1} return {}
  125. set buf [redis_readnl $fd $count]
  126. return $buf
  127. }
  128. proc ::redis::redis_multi_bulk_read fd {
  129. set count [redis_read_line $fd]
  130. if {$count == -1} return {}
  131. set l {}
  132. set err {}
  133. for {set i 0} {$i < $count} {incr i} {
  134. if {[catch {
  135. lappend l [redis_read_reply $fd]
  136. } e] && $err eq {}} {
  137. set err $e
  138. }
  139. }
  140. if {$err ne {}} {return -code error $err}
  141. return $l
  142. }
  143. proc ::redis::redis_read_line fd {
  144. string trim [gets $fd]
  145. }
  146. proc ::redis::redis_read_reply fd {
  147. set type [read $fd 1]
  148. switch -exact -- $type {
  149. : -
  150. + {redis_read_line $fd}
  151. - {return -code error [redis_read_line $fd]}
  152. $ {redis_bulk_read $fd}
  153. * {redis_multi_bulk_read $fd}
  154. default {return -code error "Bad protocol, '$type' as reply type byte"}
  155. }
  156. }
  157. proc ::redis::redis_reset_state id {
  158. set ::redis::state($id) [dict create buf {} mbulk -1 bulk -1 reply {}]
  159. set ::redis::statestack($id) {}
  160. }
  161. proc ::redis::redis_call_callback {id type reply} {
  162. set cb [lindex $::redis::callback($id) 0]
  163. set ::redis::callback($id) [lrange $::redis::callback($id) 1 end]
  164. uplevel #0 $cb [list ::redis::redisHandle$id $type $reply]
  165. ::redis::redis_reset_state $id
  166. }
  167. # Read a reply in non-blocking mode.
  168. proc ::redis::redis_readable {fd id} {
  169. if {[eof $fd]} {
  170. redis_call_callback $id eof {}
  171. ::redis::__method__close $id $fd
  172. return
  173. }
  174. if {[dict get $::redis::state($id) bulk] == -1} {
  175. set line [gets $fd]
  176. if {$line eq {}} return ;# No complete line available, return
  177. switch -exact -- [string index $line 0] {
  178. : -
  179. + {redis_call_callback $id reply [string range $line 1 end-1]}
  180. - {redis_call_callback $id err [string range $line 1 end-1]}
  181. $ {
  182. dict set ::redis::state($id) bulk \
  183. [expr [string range $line 1 end-1]+2]
  184. if {[dict get $::redis::state($id) bulk] == 1} {
  185. # We got a $-1, hack the state to play well with this.
  186. dict set ::redis::state($id) bulk 2
  187. dict set ::redis::state($id) buf "\r\n"
  188. ::redis::redis_readable $fd $id
  189. }
  190. }
  191. * {
  192. dict set ::redis::state($id) mbulk [string range $line 1 end-1]
  193. # Handle *-1
  194. if {[dict get $::redis::state($id) mbulk] == -1} {
  195. redis_call_callback $id reply {}
  196. }
  197. }
  198. default {
  199. redis_call_callback $id err \
  200. "Bad protocol, $type as reply type byte"
  201. }
  202. }
  203. } else {
  204. set totlen [dict get $::redis::state($id) bulk]
  205. set buflen [string length [dict get $::redis::state($id) buf]]
  206. set toread [expr {$totlen-$buflen}]
  207. set data [read $fd $toread]
  208. set nread [string length $data]
  209. dict append ::redis::state($id) buf $data
  210. # Check if we read a complete bulk reply
  211. if {[string length [dict get $::redis::state($id) buf]] ==
  212. [dict get $::redis::state($id) bulk]} {
  213. if {[dict get $::redis::state($id) mbulk] == -1} {
  214. redis_call_callback $id reply \
  215. [string range [dict get $::redis::state($id) buf] 0 end-2]
  216. } else {
  217. dict with ::redis::state($id) {
  218. lappend reply [string range $buf 0 end-2]
  219. incr mbulk -1
  220. set bulk -1
  221. }
  222. if {[dict get $::redis::state($id) mbulk] == 0} {
  223. redis_call_callback $id reply \
  224. [dict get $::redis::state($id) reply]
  225. }
  226. }
  227. }
  228. }
  229. }