You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

325 lines
12 KiB

4 years ago
  1. #if MYSQL_6_10
  2. // Copyright © 2004, 2016, Oracle and/or its affiliates. All rights reserved.
  3. //
  4. // MySQL Connector/NET is licensed under the terms of the GPLv2
  5. // <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
  6. // MySQL Connectors. There are special exceptions to the terms and
  7. // conditions of the GPLv2 as it is applied to this software, see the
  8. // FLOSS License Exception
  9. // <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
  10. //
  11. // This program is free software; you can redistribute it and/or modify
  12. // it under the terms of the GNU General Public License as published
  13. // by the Free Software Foundation; version 2 of the License.
  14. //
  15. // This program is distributed in the hope that it will be useful, but
  16. // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  17. // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  18. // for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License along
  21. // with this program; if not, write to the Free Software Foundation, Inc.,
  22. // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  23. using System;
  24. using System.Collections.Generic;
  25. using System.IO;
  26. using System.Text;
  27. namespace Externals.MySql.Data.MySqlClient.Memcached
  28. {
  29. /// <summary>
  30. /// Implementation of the Memcached text client protocol.
  31. /// </summary>
  32. internal class TextClient : Client
  33. {
  34. private readonly Encoding _encoding;
  35. private static readonly string PROTOCOL_ADD = "add";
  36. private static readonly string PROTOCOL_APPEND = "append";
  37. private static readonly string PROTOCOL_CAS = "cas";
  38. private static readonly string PROTOCOL_DECREMENT = "decr";
  39. private static readonly string PROTOCOL_DELETE = "delete";
  40. private static readonly string PROTOCOL_FLUSHALL = "flush_all";
  41. private static readonly string PROTOCOL_GETS = "gets";
  42. private static readonly string PROTOCOL_INCREMENT = "incr";
  43. private static readonly string PROTOCOL_PREPEND = "prepend";
  44. private static readonly string PROTOCOL_REPLACE = "replace";
  45. private static readonly string PROTOCOL_SET = "set";
  46. private static readonly string VALUE = "VALUE";
  47. private static readonly string END = "END";
  48. // Errors
  49. private static readonly string ERR_ERROR = "ERROR";
  50. private static readonly string ERR_CLIENT_ERROR = "CLIENT_ERROR";
  51. private static readonly string ERR_SERVER_ERROR = "SERVER_ERROR";
  52. protected internal TextClient(string server, uint port) : base(server, port)
  53. {
  54. _encoding = Encoding.UTF8;
  55. }
  56. #region Memcached protocol interface
  57. public override void Add(string key, object data, TimeSpan expiration)
  58. {
  59. SendCommand(PROTOCOL_ADD, key, data, expiration);
  60. }
  61. public override void Append(string key, object data)
  62. {
  63. SendCommand(PROTOCOL_APPEND, key, data);
  64. }
  65. public override void Cas(string key, object data, TimeSpan expiration, ulong casUnique)
  66. {
  67. SendCommand(PROTOCOL_CAS, key, data, expiration, casUnique);
  68. }
  69. public override void Decrement(string key, int amount)
  70. {
  71. SendCommand(PROTOCOL_DECREMENT, key, amount);
  72. }
  73. public override void Delete(string key)
  74. {
  75. SendCommand(PROTOCOL_DELETE, key);
  76. }
  77. public override void FlushAll(TimeSpan delay)
  78. {
  79. SendCommand(PROTOCOL_FLUSHALL, delay);
  80. }
  81. public override KeyValuePair<string, object> Get(string key)
  82. {
  83. KeyValuePair<string, object>[] kvp = Gets(key);
  84. if (kvp.Length == 0)
  85. throw new MemcachedException("Item does not exists.");
  86. else
  87. return kvp[0];
  88. }
  89. private KeyValuePair<string, object>[] Gets(params string[] keys)
  90. {
  91. StringBuilder sb = new StringBuilder();
  92. sb.Append(string.Format("{0}", PROTOCOL_GETS));
  93. for (int i = 0; i < keys.Length; i++)
  94. {
  95. sb.Append(string.Format(" {0}", keys[i]));
  96. }
  97. sb.Append("\r\n");
  98. SendData(sb.ToString());
  99. byte[] res = GetResponse();
  100. return ParseGetResponse(res);
  101. }
  102. public override void Increment(string key, int amount)
  103. {
  104. SendCommand(PROTOCOL_INCREMENT, key, amount);
  105. }
  106. public override void Prepend(string key, object data)
  107. {
  108. SendCommand(PROTOCOL_PREPEND, key, data);
  109. }
  110. public override void Replace(string key, object data, TimeSpan expiration)
  111. {
  112. SendCommand(PROTOCOL_REPLACE, key, data, expiration);
  113. }
  114. public override void Set(string key, object data, TimeSpan expiration)
  115. {
  116. SendCommand(PROTOCOL_SET, key, data, expiration);
  117. }
  118. #endregion
  119. #region Support methods
  120. /// <summary>
  121. /// Sends a command to the memcached server.
  122. /// </summary>
  123. /// <param name="cmd"></param>
  124. /// <param name="key"></param>
  125. /// <param name="data"></param>
  126. /// <param name="expiration"></param>
  127. /// <param name="casUnique"></param>
  128. /// <remarks>This version is for commands that take a key, data, expiration and casUnique.</remarks>
  129. private void SendCommand(string cmd, string key, object data, TimeSpan expiration, ulong casUnique)
  130. {
  131. StringBuilder sb = new StringBuilder();
  132. // set key flags exptime
  133. sb.Append(string.Format("{0} {1} 0 {2} ", cmd, key, (int)(expiration.TotalSeconds)));
  134. byte[] buf = _encoding.GetBytes(data.ToString());
  135. string s = _encoding.GetString(buf, 0, buf.Length);
  136. sb.Append(s.Length.ToString());
  137. sb.AppendFormat(" {0}", casUnique);
  138. sb.Append("\r\n");
  139. sb.Append(s);
  140. sb.Append("\r\n");
  141. SendData(sb.ToString());
  142. GetResponse();
  143. }
  144. /// <summary>
  145. /// Sends a command to the memcached server.
  146. /// </summary>
  147. /// <param name="cmd"></param>
  148. /// <param name="key"></param>
  149. /// <param name="data"></param>
  150. /// <param name="expiration"></param>
  151. /// <remarks>This version is for commands that take a key, data and expiration</remarks>
  152. private void SendCommand(string cmd, string key, object data, TimeSpan expiration)
  153. {
  154. StringBuilder sb = new StringBuilder();
  155. // set key flags exptime
  156. sb.Append(string.Format("{0} {1} 0 {2} ", cmd, key, (int)(expiration.TotalSeconds)));
  157. byte[] buf = _encoding.GetBytes(data.ToString());
  158. string s = _encoding.GetString(buf, 0, buf.Length);
  159. sb.Append(s.Length.ToString());
  160. sb.Append("\r\n");
  161. sb.Append(s);
  162. sb.Append("\r\n");
  163. SendData(sb.ToString());
  164. GetResponse();
  165. }
  166. /// <summary>
  167. /// Send a command to memcached server.
  168. /// </summary>
  169. /// <param name="cmd"></param>
  170. /// <param name="key"></param>
  171. /// <param name="data"></param>
  172. /// <remarks>This version is for commands that don't need flags neither expiration fields.</remarks>
  173. private void SendCommand(string cmd, string key, object data)
  174. {
  175. StringBuilder sb = new StringBuilder();
  176. // set key
  177. sb.Append(string.Format("{0} {1} ", cmd, key));
  178. byte[] buf = _encoding.GetBytes(data.ToString());
  179. string s = _encoding.GetString(buf, 0, buf.Length);
  180. if ((cmd == PROTOCOL_APPEND) || (cmd == PROTOCOL_PREPEND))
  181. {
  182. sb.Append("0 0 ");
  183. }
  184. sb.Append(s.Length.ToString());
  185. sb.Append("\r\n");
  186. sb.Append(s);
  187. sb.Append("\r\n");
  188. SendData(sb.ToString());
  189. GetResponse();
  190. }
  191. /// <summary>
  192. /// Sends a command to the server.
  193. /// </summary>
  194. /// <param name="cmd"></param>
  195. /// <param name="key"></param>
  196. /// <remarks>This version is for commands that only require a key</remarks>
  197. private void SendCommand(string cmd, string key)
  198. {
  199. StringBuilder sb = new StringBuilder();
  200. // set key
  201. sb.Append(string.Format("{0} {1} ", cmd, key));
  202. sb.Append("\r\n");
  203. SendData(sb.ToString());
  204. GetResponse();
  205. }
  206. /// <summary>
  207. /// Sends a command to the server.
  208. /// </summary>
  209. /// <param name="cmd"></param>
  210. /// <param name="key"></param>
  211. /// <param name="amount"></param>
  212. /// <remarks>This version is for commands that only require a key and an integer value.</remarks>
  213. private void SendCommand(string cmd, string key, int amount)
  214. {
  215. StringBuilder sb = new StringBuilder();
  216. // set key
  217. sb.Append(string.Format("{0} {1} {2}", cmd, key, amount));
  218. sb.Append("\r\n");
  219. SendData(sb.ToString());
  220. GetResponse();
  221. }
  222. /// <summary>
  223. /// Sends a command to the server.
  224. /// </summary>
  225. /// <param name="cmd"></param>
  226. /// <param name="expiration"></param>
  227. /// <remarks>This version is for commands that only require a key and expiration.</remarks>
  228. private void SendCommand(string cmd, TimeSpan expiration)
  229. {
  230. StringBuilder sb = new StringBuilder();
  231. sb.Append(string.Format("{0} {1}\r\n", PROTOCOL_FLUSHALL, expiration.TotalSeconds));
  232. SendData(sb.ToString());
  233. GetResponse();
  234. }
  235. private void ValidateErrorResponse(byte[] res)
  236. {
  237. string s = _encoding.GetString(res, 0, res.Length);
  238. if ((s.StartsWith(ERR_ERROR, StringComparison.OrdinalIgnoreCase)) ||
  239. (s.StartsWith(ERR_CLIENT_ERROR, StringComparison.OrdinalIgnoreCase)) ||
  240. (s.StartsWith(ERR_SERVER_ERROR, StringComparison.OrdinalIgnoreCase)))
  241. {
  242. throw new MemcachedException(s);
  243. }
  244. }
  245. private void SendData(string sData)
  246. {
  247. byte[] data = _encoding.GetBytes(sData);
  248. stream.Write(data, 0, data.Length);
  249. }
  250. private KeyValuePair<string, object>[] ParseGetResponse(byte[] input)
  251. {
  252. // VALUE key2 10 9 2\r\n111222333\r\nEND\r\n
  253. string[] sInput = _encoding.GetString(input, 0, input.Length).Split(new string[] { "\r\n" }, StringSplitOptions.None);
  254. List<KeyValuePair<string, object>> l = new List<KeyValuePair<string, object>>();
  255. int i = 0;
  256. string key = "";
  257. KeyValuePair<string, object> kvp;
  258. while ((sInput[i] != END) && (i < sInput.Length))
  259. {
  260. if (sInput[i].StartsWith(VALUE, StringComparison.OrdinalIgnoreCase))
  261. {
  262. key = sInput[i].Split(' ')[1];
  263. }
  264. else
  265. {
  266. kvp = new KeyValuePair<string, object>(key, sInput[i]);
  267. l.Add(kvp);
  268. }
  269. i++;
  270. }
  271. return l.ToArray();
  272. }
  273. private byte[] GetResponse()
  274. {
  275. byte[] res = new byte[1024];
  276. MemoryStream ms = new MemoryStream();
  277. int cnt = stream.Read(res, 0, 1024);
  278. while (cnt > 0)
  279. {
  280. ms.Write(res, 0, cnt);
  281. if (cnt < 1024) break;
  282. cnt = stream.Read(res, 0, 1024);
  283. }
  284. byte[] res2 = ms.ToArray();
  285. ValidateErrorResponse(res2);
  286. return res2;
  287. }
  288. #endregion
  289. }
  290. }
  291. #endif