evented-memcache-client 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +113 -0
- data/COPYING +348 -0
- data/README +75 -0
- data/Rakefile +123 -0
- data/doc/classes/EventMachine.html +146 -0
- data/doc/classes/EventMachine/Protocols.html +135 -0
- data/doc/classes/EventMachine/Protocols/Memcache.html +153 -0
- data/doc/classes/EventMachine/Protocols/Memcache/Client.html +121 -0
- data/doc/classes/EventMachine/Protocols/Memcache/Connectable.html +615 -0
- data/doc/classes/EventMachine/Protocols/Memcache/Connection.html +192 -0
- data/doc/classes/EventMachine/Protocols/Memcache/Sender.html +389 -0
- data/doc/classes/EventMachine/Protocols/Memcache/Server.html +463 -0
- data/doc/classes/EventMachine/Protocols/Memcache/TenaciousMC.html +277 -0
- data/doc/classes/MyHandler.html +197 -0
- data/doc/created.rid +1 -0
- data/doc/files/COPYING.html +473 -0
- data/doc/files/README.html +187 -0
- data/doc/files/extras/consumer_rb.html +110 -0
- data/doc/files/extras/producer_rb.html +110 -0
- data/doc/files/extras/server_rb.html +110 -0
- data/doc/files/lib/evented-memcache-client_rb.html +125 -0
- data/doc/files/lib/evented_memcache_client/client_rb.html +120 -0
- data/doc/files/lib/evented_memcache_client/connectable_rb.html +120 -0
- data/doc/files/lib/evented_memcache_client/connection_rb.html +120 -0
- data/doc/files/lib/evented_memcache_client/sender_rb.html +112 -0
- data/doc/files/lib/evented_memcache_client/server_rb.html +120 -0
- data/doc/files/lib/evented_memcache_client/tenacious_rb.html +120 -0
- data/doc/fr_class_index.html +36 -0
- data/doc/fr_file_index.html +38 -0
- data/doc/fr_method_index.html +66 -0
- data/doc/index.html +24 -0
- data/doc/rdoc-style.css +208 -0
- data/extras/consumer.rb +41 -0
- data/extras/producer.rb +22 -0
- data/extras/server.rb +33 -0
- data/lib/evented-memcache-client.rb +37 -0
- data/lib/evented_memcache_client/client.rb +36 -0
- data/lib/evented_memcache_client/connectable.rb +324 -0
- data/lib/evented_memcache_client/connection.rb +73 -0
- data/lib/evented_memcache_client/sender.rb +184 -0
- data/lib/evented_memcache_client/server.rb +118 -0
- data/lib/evented_memcache_client/tenacious.rb +106 -0
- metadata +115 -0
data/doc/index.html
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
<?xml version="1.0" encoding="iso-8859-1"?>
|
2
|
+
<!DOCTYPE html
|
3
|
+
PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
|
4
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
|
5
|
+
|
6
|
+
<!--
|
7
|
+
|
8
|
+
Evented Memcache Client
|
9
|
+
|
10
|
+
-->
|
11
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
12
|
+
<head>
|
13
|
+
<title>Evented Memcache Client</title>
|
14
|
+
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
|
15
|
+
</head>
|
16
|
+
<frameset rows="20%, 80%">
|
17
|
+
<frameset cols="25%,35%,45%">
|
18
|
+
<frame src="fr_file_index.html" title="Files" name="Files" />
|
19
|
+
<frame src="fr_class_index.html" name="Classes" />
|
20
|
+
<frame src="fr_method_index.html" name="Methods" />
|
21
|
+
</frameset>
|
22
|
+
<frame src="files/README.html" name="docwin" />
|
23
|
+
</frameset>
|
24
|
+
</html>
|
data/doc/rdoc-style.css
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
|
2
|
+
body {
|
3
|
+
font-family: Verdana,Arial,Helvetica,sans-serif;
|
4
|
+
font-size: 90%;
|
5
|
+
margin: 0;
|
6
|
+
margin-left: 40px;
|
7
|
+
padding: 0;
|
8
|
+
background: white;
|
9
|
+
}
|
10
|
+
|
11
|
+
h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; }
|
12
|
+
h1 { font-size: 150%; }
|
13
|
+
h2,h3,h4 { margin-top: 1em; }
|
14
|
+
|
15
|
+
a { background: #eef; color: #039; text-decoration: none; }
|
16
|
+
a:hover { background: #039; color: #eef; }
|
17
|
+
|
18
|
+
/* Override the base stylesheet's Anchor inside a table cell */
|
19
|
+
td > a {
|
20
|
+
background: transparent;
|
21
|
+
color: #039;
|
22
|
+
text-decoration: none;
|
23
|
+
}
|
24
|
+
|
25
|
+
/* and inside a section title */
|
26
|
+
.section-title > a {
|
27
|
+
background: transparent;
|
28
|
+
color: #eee;
|
29
|
+
text-decoration: none;
|
30
|
+
}
|
31
|
+
|
32
|
+
/* === Structural elements =================================== */
|
33
|
+
|
34
|
+
div#index {
|
35
|
+
margin: 0;
|
36
|
+
margin-left: -40px;
|
37
|
+
padding: 0;
|
38
|
+
font-size: 90%;
|
39
|
+
}
|
40
|
+
|
41
|
+
|
42
|
+
div#index a {
|
43
|
+
margin-left: 0.7em;
|
44
|
+
}
|
45
|
+
|
46
|
+
div#index .section-bar {
|
47
|
+
margin-left: 0px;
|
48
|
+
padding-left: 0.7em;
|
49
|
+
background: #ccc;
|
50
|
+
font-size: small;
|
51
|
+
}
|
52
|
+
|
53
|
+
|
54
|
+
div#classHeader, div#fileHeader {
|
55
|
+
width: auto;
|
56
|
+
color: white;
|
57
|
+
padding: 0.5em 1.5em 0.5em 1.5em;
|
58
|
+
margin: 0;
|
59
|
+
margin-left: -40px;
|
60
|
+
border-bottom: 3px solid #006;
|
61
|
+
}
|
62
|
+
|
63
|
+
div#classHeader a, div#fileHeader a {
|
64
|
+
background: inherit;
|
65
|
+
color: white;
|
66
|
+
}
|
67
|
+
|
68
|
+
div#classHeader td, div#fileHeader td {
|
69
|
+
background: inherit;
|
70
|
+
color: white;
|
71
|
+
}
|
72
|
+
|
73
|
+
|
74
|
+
div#fileHeader {
|
75
|
+
background: #057;
|
76
|
+
}
|
77
|
+
|
78
|
+
div#classHeader {
|
79
|
+
background: #048;
|
80
|
+
}
|
81
|
+
|
82
|
+
|
83
|
+
.class-name-in-header {
|
84
|
+
font-size: 180%;
|
85
|
+
font-weight: bold;
|
86
|
+
}
|
87
|
+
|
88
|
+
|
89
|
+
div#bodyContent {
|
90
|
+
padding: 0 1.5em 0 1.5em;
|
91
|
+
}
|
92
|
+
|
93
|
+
div#description {
|
94
|
+
padding: 0.5em 1.5em;
|
95
|
+
background: #efefef;
|
96
|
+
border: 1px dotted #999;
|
97
|
+
}
|
98
|
+
|
99
|
+
div#description h1,h2,h3,h4,h5,h6 {
|
100
|
+
color: #125;;
|
101
|
+
background: transparent;
|
102
|
+
}
|
103
|
+
|
104
|
+
div#validator-badges {
|
105
|
+
text-align: center;
|
106
|
+
}
|
107
|
+
div#validator-badges img { border: 0; }
|
108
|
+
|
109
|
+
div#copyright {
|
110
|
+
color: #333;
|
111
|
+
background: #efefef;
|
112
|
+
font: 0.75em sans-serif;
|
113
|
+
margin-top: 5em;
|
114
|
+
margin-bottom: 0;
|
115
|
+
padding: 0.5em 2em;
|
116
|
+
}
|
117
|
+
|
118
|
+
|
119
|
+
/* === Classes =================================== */
|
120
|
+
|
121
|
+
table.header-table {
|
122
|
+
color: white;
|
123
|
+
font-size: small;
|
124
|
+
}
|
125
|
+
|
126
|
+
.type-note {
|
127
|
+
font-size: small;
|
128
|
+
color: #DEDEDE;
|
129
|
+
}
|
130
|
+
|
131
|
+
.xxsection-bar {
|
132
|
+
background: #eee;
|
133
|
+
color: #333;
|
134
|
+
padding: 3px;
|
135
|
+
}
|
136
|
+
|
137
|
+
.section-bar {
|
138
|
+
color: #333;
|
139
|
+
border-bottom: 1px solid #999;
|
140
|
+
margin-left: -20px;
|
141
|
+
}
|
142
|
+
|
143
|
+
|
144
|
+
.section-title {
|
145
|
+
background: #79a;
|
146
|
+
color: #eee;
|
147
|
+
padding: 3px;
|
148
|
+
margin-top: 2em;
|
149
|
+
margin-left: -30px;
|
150
|
+
border: 1px solid #999;
|
151
|
+
}
|
152
|
+
|
153
|
+
.top-aligned-row { vertical-align: top }
|
154
|
+
.bottom-aligned-row { vertical-align: bottom }
|
155
|
+
|
156
|
+
/* --- Context section classes ----------------------- */
|
157
|
+
|
158
|
+
.context-row { }
|
159
|
+
.context-item-name { font-family: monospace; font-weight: bold; color: black; }
|
160
|
+
.context-item-value { font-size: small; color: #448; }
|
161
|
+
.context-item-desc { color: #333; padding-left: 2em; }
|
162
|
+
|
163
|
+
/* --- Method classes -------------------------- */
|
164
|
+
.method-detail {
|
165
|
+
background: #efefef;
|
166
|
+
padding: 0;
|
167
|
+
margin-top: 0.5em;
|
168
|
+
margin-bottom: 1em;
|
169
|
+
border: 1px dotted #ccc;
|
170
|
+
}
|
171
|
+
.method-heading {
|
172
|
+
color: black;
|
173
|
+
background: #ccc;
|
174
|
+
border-bottom: 1px solid #666;
|
175
|
+
padding: 0.2em 0.5em 0 0.5em;
|
176
|
+
}
|
177
|
+
.method-signature { color: black; background: inherit; }
|
178
|
+
.method-name { font-weight: bold; }
|
179
|
+
.method-args { font-style: italic; }
|
180
|
+
.method-description { padding: 0 0.5em 0 0.5em; }
|
181
|
+
|
182
|
+
/* --- Source code sections -------------------- */
|
183
|
+
|
184
|
+
a.source-toggle { font-size: 90%; }
|
185
|
+
div.method-source-code {
|
186
|
+
background: #262626;
|
187
|
+
color: #ffdead;
|
188
|
+
margin: 1em;
|
189
|
+
padding: 0.5em;
|
190
|
+
border: 1px dashed #999;
|
191
|
+
overflow: hidden;
|
192
|
+
}
|
193
|
+
|
194
|
+
div.method-source-code pre { color: #ffdead; overflow: hidden; }
|
195
|
+
|
196
|
+
/* --- Ruby keyword styles --------------------- */
|
197
|
+
|
198
|
+
.standalone-code { background: #221111; color: #ffdead; overflow: hidden; }
|
199
|
+
|
200
|
+
.ruby-constant { color: #7fffd4; background: transparent; }
|
201
|
+
.ruby-keyword { color: #00ffff; background: transparent; }
|
202
|
+
.ruby-ivar { color: #eedd82; background: transparent; }
|
203
|
+
.ruby-operator { color: #00ffee; background: transparent; }
|
204
|
+
.ruby-identifier { color: #ffdead; background: transparent; }
|
205
|
+
.ruby-node { color: #ffa07a; background: transparent; }
|
206
|
+
.ruby-comment { color: #b22222; font-weight: bold; background: transparent; }
|
207
|
+
.ruby-regexp { color: #ffa07a; background: transparent; }
|
208
|
+
.ruby-value { color: #7fffd4; background: transparent; }
|
data/extras/consumer.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'eventmachine'
|
5
|
+
require 'evented-memcache-client'
|
6
|
+
|
7
|
+
module MyHandler
|
8
|
+
def handle_open(conn)
|
9
|
+
@opened_at = Time.now
|
10
|
+
puts 'Opened connection. Fetching...'
|
11
|
+
end
|
12
|
+
def handle_close(conn)
|
13
|
+
if @opened_at
|
14
|
+
puts 'Lost connection to memcached.'
|
15
|
+
else
|
16
|
+
puts 'Failed to open connection to memcached, retrying.'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
def handle_value(conn, data, args)
|
20
|
+
key = args[0]
|
21
|
+
flags = args[1]
|
22
|
+
puts "Received message: '#{data}'"
|
23
|
+
puts "Fetching more... #{@key}."
|
24
|
+
conn.get(:key => key)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
host = 'localhost'
|
29
|
+
port = 12345
|
30
|
+
EventMachine::run {
|
31
|
+
EventMachine::connect(host,
|
32
|
+
port,
|
33
|
+
EventMachine::Protocols::Memcache::TenaciousMC,
|
34
|
+
MyHandler,
|
35
|
+
{
|
36
|
+
:host => host,
|
37
|
+
:port => port,
|
38
|
+
:eager => true,
|
39
|
+
:key => 'x'
|
40
|
+
})
|
41
|
+
}
|
data/extras/producer.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'eventmachine'
|
5
|
+
require 'evented-memcache-client'
|
6
|
+
|
7
|
+
hash = {
|
8
|
+
:open => Proc.new { |conn|
|
9
|
+
p 'open'
|
10
|
+
conn.set(:key => 'x', :data => Time.now.to_s)
|
11
|
+
},
|
12
|
+
:stored => Proc.new { |conn, data, args|
|
13
|
+
puts "Stored a value"
|
14
|
+
EventMachine::stop
|
15
|
+
},
|
16
|
+
}
|
17
|
+
EventMachine::run {
|
18
|
+
EventMachine::connect('127.0.0.1',
|
19
|
+
12345,
|
20
|
+
EventMachine::Protocols::Memcache::Client,
|
21
|
+
hash)
|
22
|
+
}
|
data/extras/server.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'eventmachine'
|
5
|
+
require 'evented-memcache-client'
|
6
|
+
|
7
|
+
# This is an example dummy server implemented using
|
8
|
+
# EventMachine::Protocols::Memcache::Server.
|
9
|
+
#
|
10
|
+
# Run it without command line arguments. It will accept client
|
11
|
+
# connections, and only handles the memcache protocol message 'set',
|
12
|
+
# which will cause it to print out the first 20 bytes of the payload
|
13
|
+
# sent by the client. It responds to the client connection with a
|
14
|
+
# 'stored'.
|
15
|
+
|
16
|
+
server = EventMachine::Protocols::Memcache::Server.new
|
17
|
+
class << server
|
18
|
+
def set(conn, data, args)
|
19
|
+
puts "set: #{data[0..20]}"
|
20
|
+
conn.stored
|
21
|
+
end
|
22
|
+
def get(conn, data, args)
|
23
|
+
data = 'You betcha'
|
24
|
+
conn.value(:key=>args[0],
|
25
|
+
:flags => 0,
|
26
|
+
:bytes => data.length,
|
27
|
+
:data => data)
|
28
|
+
conn.end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
EventMachine::run {
|
32
|
+
server.start
|
33
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
#############################################################################
|
2
|
+
#
|
3
|
+
# Author:: Colin Steele (colin@colinsteele.org)
|
4
|
+
# Homepage::
|
5
|
+
#
|
6
|
+
#----------------------------------------------------------------------------
|
7
|
+
#
|
8
|
+
# Copyright (C) 2008 by Colin Steele. All Rights Reserved.
|
9
|
+
# colin@colinsteele.org
|
10
|
+
#
|
11
|
+
# This program is free software; you can redistribute it and/or modify
|
12
|
+
# it under the terms of either: 1) the GNU General Public License
|
13
|
+
# as published by the Free Software Foundation; either version 2 of the
|
14
|
+
# License, or (at your option) any later version; or 2) Ruby's License.
|
15
|
+
#
|
16
|
+
# See the file COPYING for complete licensing information.
|
17
|
+
#
|
18
|
+
#---------------------------------------------------------------------------
|
19
|
+
#
|
20
|
+
#
|
21
|
+
#############################################################################
|
22
|
+
|
23
|
+
require 'strscan'
|
24
|
+
require 'evented_memcache_client/sender'
|
25
|
+
require 'evented_memcache_client/connectable'
|
26
|
+
require 'evented_memcache_client/connection'
|
27
|
+
require 'evented_memcache_client/client'
|
28
|
+
require 'evented_memcache_client/tenacious'
|
29
|
+
require 'evented_memcache_client/server'
|
30
|
+
|
31
|
+
module EventMachine
|
32
|
+
module Protocols
|
33
|
+
module Memcache
|
34
|
+
VERSION = "1.0.0"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#############################################################################
|
2
|
+
#
|
3
|
+
# Author:: Colin Steele (colin@colinsteele.org)
|
4
|
+
# Homepage::
|
5
|
+
#
|
6
|
+
#----------------------------------------------------------------------------
|
7
|
+
#
|
8
|
+
# Copyright (C) 2008 by Colin Steele. All Rights Reserved.
|
9
|
+
# colin@colinsteele.org
|
10
|
+
#
|
11
|
+
# This program is free software; you can redistribute it and/or modify
|
12
|
+
# it under the terms of either: 1) the GNU General Public License
|
13
|
+
# as published by the Free Software Foundation; either version 2 of the
|
14
|
+
# License, or (at your option) any later version; or 2) Ruby's License.
|
15
|
+
#
|
16
|
+
# See the file COPYING for complete licensing information.
|
17
|
+
#
|
18
|
+
#---------------------------------------------------------------------------
|
19
|
+
#
|
20
|
+
#
|
21
|
+
#############################################################################
|
22
|
+
|
23
|
+
require 'rubygems'
|
24
|
+
require 'eventmachine'
|
25
|
+
|
26
|
+
module EventMachine
|
27
|
+
module Protocols
|
28
|
+
module Memcache
|
29
|
+
# A functional (though simple) client-side implementation of the
|
30
|
+
# memcache protocol for EventMachine-based systems. See
|
31
|
+
# extras/producer.rb and extras/consumer.rb for examples.
|
32
|
+
class Client < Connection
|
33
|
+
end # Client
|
34
|
+
end # Memcache
|
35
|
+
end # module Protocols
|
36
|
+
end # module EventMachine
|
@@ -0,0 +1,324 @@
|
|
1
|
+
#############################################################################
|
2
|
+
#
|
3
|
+
# Author:: Colin Steele (colin@colinsteele.org)
|
4
|
+
# Homepage::
|
5
|
+
#
|
6
|
+
#----------------------------------------------------------------------------
|
7
|
+
#
|
8
|
+
# Copyright (C) 2008 by Colin Steele. All Rights Reserved.
|
9
|
+
# colin@colinsteele.org
|
10
|
+
#
|
11
|
+
# This program is free software; you can redistribute it and/or modify
|
12
|
+
# it under the terms of either: 1) the GNU General Public License
|
13
|
+
# as published by the Free Software Foundation; either version 2 of the
|
14
|
+
# License, or (at your option) any later version; or 2) Ruby's License.
|
15
|
+
#
|
16
|
+
# See the file COPYING for complete licensing information.
|
17
|
+
#
|
18
|
+
#---------------------------------------------------------------------------
|
19
|
+
#
|
20
|
+
#
|
21
|
+
#############################################################################
|
22
|
+
|
23
|
+
require 'rubygems'
|
24
|
+
require 'eventmachine'
|
25
|
+
|
26
|
+
module EventMachine
|
27
|
+
module Protocols
|
28
|
+
module Memcache
|
29
|
+
|
30
|
+
# Included by Connection to implement the interface to
|
31
|
+
# EventMachine::Connection; handles the opening/closing of the
|
32
|
+
# connection, and when data is received from the remote
|
33
|
+
# endpoint. Parses messages and delivers callbacks to higher
|
34
|
+
# level application code.
|
35
|
+
|
36
|
+
module Connectable
|
37
|
+
|
38
|
+
HEADER_REGEX = /([^\r\n]+?)\r\n/m
|
39
|
+
|
40
|
+
include Sender
|
41
|
+
|
42
|
+
# Install callbacks.
|
43
|
+
#
|
44
|
+
# +handler+ may be a Hash of Procs, a Module, or an object.
|
45
|
+
# If it's a hash of Procs, each key is a callback like :open,
|
46
|
+
# :close, :unknown_message, and :foo, where foo is a memcache
|
47
|
+
# protocol message (eg, :stored).
|
48
|
+
#
|
49
|
+
# If +handler+ is a Module, it is assumed that it defines a
|
50
|
+
# number of methods like handle_open, handle_close, and
|
51
|
+
# handle_foo, where foo is a is a memcache protocol message
|
52
|
+
# (eg, handle_stored). +handler+ is then +include+'d, making
|
53
|
+
# its methods available as callbacks.
|
54
|
+
#
|
55
|
+
# If +handler+ is an object, Connectable will invoke methods
|
56
|
+
# on it corresponding to the abovementioned. Eg., :open,
|
57
|
+
# :closed, :unknown_message, and :<memcache_message>, like
|
58
|
+
# :stored, :value, :end, etc.
|
59
|
+
#
|
60
|
+
def set_handler(handler)
|
61
|
+
@handler = nil
|
62
|
+
@callbacks = {}
|
63
|
+
if handler.respond_to?(:keys)
|
64
|
+
@callbacks = handler
|
65
|
+
elsif handler.is_a?(Module)
|
66
|
+
self.class.class_eval {
|
67
|
+
include handler
|
68
|
+
}
|
69
|
+
else
|
70
|
+
@handler = handler
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Called first(?) reactor spin after instantiated.
|
75
|
+
def post_init
|
76
|
+
@data = ""
|
77
|
+
|
78
|
+
# Stats
|
79
|
+
|
80
|
+
@rcvs = 0
|
81
|
+
@msgs_in = 0
|
82
|
+
@msgs_out = 0
|
83
|
+
@rcv_stats = {}
|
84
|
+
@snd_stats = {}
|
85
|
+
@opened_at = nil
|
86
|
+
@closed_at = nil
|
87
|
+
end
|
88
|
+
|
89
|
+
# Called after connection established. NB: not called for
|
90
|
+
# passive (server) connections
|
91
|
+
def connection_completed
|
92
|
+
@opened_at = Time.now
|
93
|
+
handle(:open, self)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Called from EM::Connection when we have data
|
97
|
+
def receive_data(data)
|
98
|
+
@rcvs += 1
|
99
|
+
@data << data
|
100
|
+
parse_incoming_data
|
101
|
+
end
|
102
|
+
|
103
|
+
# Called from EM::Connection when the connection is dead.
|
104
|
+
def unbind
|
105
|
+
@closed_at = Time.now
|
106
|
+
@data = nil
|
107
|
+
handle(:close, self)
|
108
|
+
end
|
109
|
+
|
110
|
+
######################################################################
|
111
|
+
# Message parsing routines
|
112
|
+
######################################################################
|
113
|
+
|
114
|
+
# See if we can pull a header out of the incoming data buffer
|
115
|
+
def parse_incoming_data
|
116
|
+
@scanner = StringScanner.new(@data)
|
117
|
+
while (header = @scanner.scan(HEADER_REGEX)) do
|
118
|
+
len = @data.length
|
119
|
+
parse_header(header)
|
120
|
+
if len == @data.length
|
121
|
+
break # it didn't take anything out, so there's not enough
|
122
|
+
end
|
123
|
+
@msgs_in += 1
|
124
|
+
@scanner = StringScanner.new(@data)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Figure out what the header is, and call the appropriate
|
129
|
+
# parsing command.
|
130
|
+
def parse_header(header_data)
|
131
|
+
elements = header_data.split(/\s/)
|
132
|
+
command_name = elements[0].downcase
|
133
|
+
if self.respond_to?("parse_#{command_name}")
|
134
|
+
self.send("parse_#{command_name}", elements[1..-1])
|
135
|
+
else
|
136
|
+
handle(:unknown_message, self, command_name, nil, nil)
|
137
|
+
@data.slice!(0, @scanner.pos)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
NO_ARG_MESSAGES = [
|
142
|
+
'error',
|
143
|
+
'stored',
|
144
|
+
'not_stored',
|
145
|
+
'exists',
|
146
|
+
'not_found',
|
147
|
+
'end',
|
148
|
+
'deleted',
|
149
|
+
'ok',
|
150
|
+
]
|
151
|
+
NO_ARG_MESSAGES.each { |method_name|
|
152
|
+
class_eval do
|
153
|
+
define_method "parse_#{method_name}" do |args|
|
154
|
+
receive_message(method_name, nil, nil)
|
155
|
+
@data.slice!(0, @scanner.pos)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
}
|
159
|
+
|
160
|
+
ARG_MESSAGES = [
|
161
|
+
'client_error',
|
162
|
+
'server_error',
|
163
|
+
'stat',
|
164
|
+
'version',
|
165
|
+
'get',
|
166
|
+
'gets',
|
167
|
+
]
|
168
|
+
ARG_MESSAGES.each { |method_name|
|
169
|
+
class_eval do
|
170
|
+
define_method "parse_#{method_name}" do |args|
|
171
|
+
receive_message(method_name, nil, args)
|
172
|
+
@data.slice!(0, @scanner.pos)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
}
|
176
|
+
|
177
|
+
BODY_MESSAGES = []
|
178
|
+
BODY_MESSAGES.each { |method_name|
|
179
|
+
class_eval do
|
180
|
+
define_method "parse_#{method_name}" do |args|
|
181
|
+
body_length = args[0].to_i
|
182
|
+
# Add two bytes for delimiting "\r\n"
|
183
|
+
if @scanner.rest_size >= (body_length + 2)
|
184
|
+
receive_message(method_name,
|
185
|
+
@scanner.rest[0..(body_length - 1)])
|
186
|
+
@scanner.pos = @scanner.pos + body_length + 2
|
187
|
+
@data.slice!(0, @scanner.pos)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
}
|
192
|
+
|
193
|
+
ARG_AND_BODY_MESSAGES = ['reserved']
|
194
|
+
ARG_AND_BODY_MESSAGES.each { |method_name|
|
195
|
+
class_eval do
|
196
|
+
define_method "parse_#{method_name}" do |args|
|
197
|
+
job_id = args[0]
|
198
|
+
body_length = args[1].to_i
|
199
|
+
# Add two bytes for delimiting "\r\n"
|
200
|
+
if @scanner.rest_size >= (body_length + 2)
|
201
|
+
receive_message(method_name,
|
202
|
+
@scanner.rest[0..(body_length - 1)])
|
203
|
+
@scanner.pos = @scanner.pos + body_length + 2
|
204
|
+
@data.slice!(0, @scanner.pos)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
}
|
209
|
+
|
210
|
+
ARGS_AND_BODY_MESSAGES = [
|
211
|
+
'value',
|
212
|
+
'set',
|
213
|
+
'add',
|
214
|
+
'replace',
|
215
|
+
'append',
|
216
|
+
'prepend',
|
217
|
+
]
|
218
|
+
ARGS_AND_BODY_MESSAGES.each { |method_name|
|
219
|
+
class_eval do
|
220
|
+
define_method "parse_#{method_name}" do |args|
|
221
|
+
body_length = args.last.to_i
|
222
|
+
if method_name == 'value'
|
223
|
+
# If there are 4 args, the last one is the cas value
|
224
|
+
if args.length > 3
|
225
|
+
body_length = args[-2].to_i
|
226
|
+
end
|
227
|
+
end
|
228
|
+
# Add two bytes for delimiting "\r\n"
|
229
|
+
if @scanner.rest_size >= (body_length + 2)
|
230
|
+
pos = @scanner.pos
|
231
|
+
endpos = pos + (body_length - 1)
|
232
|
+
receive_message(method_name,
|
233
|
+
@data[pos..endpos],
|
234
|
+
args)
|
235
|
+
@data.slice!(0, endpos + 3) # endpos is an index; plus "\r\n"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
}
|
240
|
+
|
241
|
+
ALL_MESSAGES =
|
242
|
+
NO_ARG_MESSAGES + ARG_MESSAGES +
|
243
|
+
BODY_MESSAGES + ARG_AND_BODY_MESSAGES + ARGS_AND_BODY_MESSAGES
|
244
|
+
|
245
|
+
# Generic "You've got mail"... Pass it off to the specific
|
246
|
+
# handler method.
|
247
|
+
def receive_message(message_name, data, args=nil)
|
248
|
+
@rcv_stats[message_name] ||= 0
|
249
|
+
@rcv_stats[message_name] += 1
|
250
|
+
if self.respond_to?("receive_#{message_name}")
|
251
|
+
self.send("receive_#{message_name}", data, args)
|
252
|
+
else
|
253
|
+
handle(:unknown_message, self, message_name, data, args)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
######################################################################
|
258
|
+
# Message interface - receiving
|
259
|
+
######################################################################
|
260
|
+
|
261
|
+
# Call the user-supplied callback method for the message we got.
|
262
|
+
ALL_MESSAGES.each{ |method_name|
|
263
|
+
class_eval do
|
264
|
+
define_method "receive_#{method_name}" do |data, args|
|
265
|
+
handle(method_name.to_sym, self, data, args)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
}
|
269
|
+
|
270
|
+
######################################################################
|
271
|
+
# Other
|
272
|
+
######################################################################
|
273
|
+
|
274
|
+
# Turns this sucker into a human-readable string with some
|
275
|
+
# semi-useful information.
|
276
|
+
def to_s
|
277
|
+
str = ''
|
278
|
+
str << "EM signature: #{signature} peer: #{remote_endpoint}\n"
|
279
|
+
str << "opened: #{@opened_at} (#{Time.now - @opened_at}s)\n"
|
280
|
+
str << "rcvs: #{@rcvs} in: #{@msgs_in} out: #{@msgs_out}\n"
|
281
|
+
@rcv_stats.each {|msg_name,count|
|
282
|
+
str << "#{msg_name} in: #{count}\n"
|
283
|
+
}
|
284
|
+
@snd_stats.each {|msg_name,count|
|
285
|
+
str << "#{msg_name} out: #{count}\n"
|
286
|
+
}
|
287
|
+
str << "I+O/sec: #{'%6.4f' % io_per_sec}."
|
288
|
+
str
|
289
|
+
end
|
290
|
+
|
291
|
+
# Begin shutting the connection down.
|
292
|
+
def start_closing
|
293
|
+
close_connection_after_writing
|
294
|
+
end
|
295
|
+
|
296
|
+
# How fast did we go? Returns a float.
|
297
|
+
def io_per_sec
|
298
|
+
total = @msgs_in + @msgs_out
|
299
|
+
(total / ((@closed_at || Time.now) - @opened_at)).to_f
|
300
|
+
end
|
301
|
+
|
302
|
+
# Call the user-supplied callback method for the message we got.
|
303
|
+
def handle(*args)
|
304
|
+
if @callbacks[args[0]]
|
305
|
+
@callbacks[args[0]].call(*args[1..-1])
|
306
|
+
elsif @handler.respond_to?(args[0])
|
307
|
+
@handler.send(*args)
|
308
|
+
elsif self.respond_to?("handle_#{args[0]}".to_sym)
|
309
|
+
self.send("handle_#{args[0]}".to_sym, *args[1..-1])
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
# Returns a nice string showing something like "192.168.1.1:32122"
|
314
|
+
def remote_endpoint
|
315
|
+
if !@peer && (peername = get_peername)
|
316
|
+
@peer = Socket.unpack_sockaddr_in(peername)
|
317
|
+
end
|
318
|
+
@peer ? "#{@peer[1]}:#{@peer[0]}" : '?:?'
|
319
|
+
end
|
320
|
+
|
321
|
+
end # Connectable
|
322
|
+
end # Memcache
|
323
|
+
end # module Protocols
|
324
|
+
end # module EventMachine
|