redis.tcl 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. # Tcl clinet library - used by test-redis.tcl script for now
  2. # Copyright (C) 2009 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::redis_write {fd buf} {
  107. puts -nonewline $fd $buf
  108. }
  109. proc ::redis::redis_writenl {fd buf} {
  110. redis_write $fd $buf
  111. redis_write $fd "\r\n"
  112. flush $fd
  113. }
  114. proc ::redis::redis_readnl {fd len} {
  115. set buf [read $fd $len]
  116. read $fd 2 ; # discard CR LF
  117. return $buf
  118. }
  119. proc ::redis::redis_bulk_read {fd} {
  120. set count [redis_read_line $fd]
  121. if {$count == -1} return {}
  122. set buf [redis_readnl $fd $count]
  123. return $buf
  124. }
  125. proc ::redis::redis_multi_bulk_read fd {
  126. set count [redis_read_line $fd]
  127. if {$count == -1} return {}
  128. set l {}
  129. for {set i 0} {$i < $count} {incr i} {
  130. lappend l [redis_read_reply $fd]
  131. }
  132. return $l
  133. }
  134. proc ::redis::redis_read_line fd {
  135. string trim [gets $fd]
  136. }
  137. proc ::redis::redis_read_reply fd {
  138. set type [read $fd 1]
  139. switch -exact -- $type {
  140. : -
  141. + {redis_read_line $fd}
  142. - {return -code error [redis_read_line $fd]}
  143. $ {redis_bulk_read $fd}
  144. * {redis_multi_bulk_read $fd}
  145. default {return -code error "Bad protocol, $type as reply type byte"}
  146. }
  147. }
  148. proc ::redis::redis_reset_state id {
  149. set ::redis::state($id) [dict create buf {} mbulk -1 bulk -1 reply {}]
  150. set ::redis::statestack($id) {}
  151. }
  152. proc ::redis::redis_call_callback {id type reply} {
  153. set cb [lindex $::redis::callback($id) 0]
  154. set ::redis::callback($id) [lrange $::redis::callback($id) 1 end]
  155. uplevel #0 $cb [list ::redis::redisHandle$id $type $reply]
  156. ::redis::redis_reset_state $id
  157. }
  158. # Read a reply in non-blocking mode.
  159. proc ::redis::redis_readable {fd id} {
  160. if {[eof $fd]} {
  161. redis_call_callback $id eof {}
  162. ::redis::__method__close $id $fd
  163. return
  164. }
  165. if {[dict get $::redis::state($id) bulk] == -1} {
  166. set line [gets $fd]
  167. if {$line eq {}} return ;# No complete line available, return
  168. switch -exact -- [string index $line 0] {
  169. : -
  170. + {redis_call_callback $id reply [string range $line 1 end-1]}
  171. - {redis_call_callback $id err [string range $line 1 end-1]}
  172. $ {
  173. dict set ::redis::state($id) bulk \
  174. [expr [string range $line 1 end-1]+2]
  175. if {[dict get $::redis::state($id) bulk] == 1} {
  176. # We got a $-1, hack the state to play well with this.
  177. dict set ::redis::state($id) bulk 2
  178. dict set ::redis::state($id) buf "\r\n"
  179. ::redis::redis_readable $fd $id
  180. }
  181. }
  182. * {
  183. dict set ::redis::state($id) mbulk [string range $line 1 end-1]
  184. # Handle *-1
  185. if {[dict get $::redis::state($id) mbulk] == -1} {
  186. redis_call_callback $id reply {}
  187. }
  188. }
  189. default {
  190. redis_call_callback $id err \
  191. "Bad protocol, $type as reply type byte"
  192. }
  193. }
  194. } else {
  195. set totlen [dict get $::redis::state($id) bulk]
  196. set buflen [string length [dict get $::redis::state($id) buf]]
  197. set toread [expr {$totlen-$buflen}]
  198. set data [read $fd $toread]
  199. set nread [string length $data]
  200. dict append ::redis::state($id) buf $data
  201. # Check if we read a complete bulk reply
  202. if {[string length [dict get $::redis::state($id) buf]] ==
  203. [dict get $::redis::state($id) bulk]} {
  204. if {[dict get $::redis::state($id) mbulk] == -1} {
  205. redis_call_callback $id reply \
  206. [string range [dict get $::redis::state($id) buf] 0 end-2]
  207. } else {
  208. dict with ::redis::state($id) {
  209. lappend reply [string range $buf 0 end-2]
  210. incr mbulk -1
  211. set bulk -1
  212. }
  213. if {[dict get $::redis::state($id) mbulk] == 0} {
  214. redis_call_callback $id reply \
  215. [dict get $::redis::state($id) reply]
  216. }
  217. }
  218. }
  219. }
  220. }