memcached.erl 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. %% @author asceth <machinist@asceth.com>
  2. %% @since 14 Aug 2008 by asceth <machinist@asceth.com>
  3. %% @doc memcached client library for erlang<br />
  4. %% All functions accept an existing socket or a tuple
  5. %% describing a host and port to connect to.
  6. %% <br />
  7. %% Variables:<br />
  8. %% <ul>
  9. %% <li>Flags -> unique integer to associate with key/value pair</li>
  10. %% <li>Expire -> time in seconds to keep a key/value pair</li>
  11. %% </ul>
  12. -module(memcached).
  13. %% External API
  14. -export([set/3, set/5]).
  15. -export([add/3, add/5]).
  16. -export([replace/3, replace/5]).
  17. -export([get/2]).
  18. -export([delete/3, delete/2]).
  19. -export([stats/1]).
  20. %%====================================================================
  21. %% Types
  22. %%====================================================================
  23. %% @type hostport() = {host, string(), port, integer()}. Tuple describing a host and port to connect to
  24. %% @type socket() = {socket, port()}. Tuple describing an existing socket
  25. %% @type memcached_connection() = hostport() | socket().
  26. %% @type memcached_key() = list() | atom().
  27. %%-type(hostport() :: {host, string(), port, integer()}).
  28. %%-type(socket() :: {socket, port()}).
  29. %%-type(memcached_connection() :: hostport() | socket()).
  30. %%-type(memcached_key() :: list() | atom()).
  31. %%====================================================================
  32. %% External API
  33. %%====================================================================
  34. %% @doc Associate Bytes with Key.
  35. %% @spec set(memcached_connection(), Key::memcached_key(), Bytes::any()) ->
  36. %% ok | {error, not_stored}
  37. %%-spec(set/3::(memcached_connection(), memcached_key(), any()) ->
  38. %% ok | {error, not_stored}).
  39. set({host, Host, port, Port}, Key, Bytes) ->
  40. set({host, Host, port, Port}, Key, 0, 0, Bytes);
  41. set({socket, Socket}, Key, Bytes) ->
  42. set({socket, Socket}, Key, 0, 0, Bytes).
  43. %% @doc Associate Bytes with Key using Flags and Expire options.
  44. %% @spec set(memcached_connection(), Key::memcached_key(), Flags::integer(), Expire::integer(), Bytes::any()) ->
  45. %% ok | {error, not_stored}
  46. %%-spec(set/5::(memcached_connection(), memcached_key(), integer(), integer(), any()) ->
  47. %% ok | {error, not_stored}).
  48. set({host, Host, port, Port}, Key, Flags, Expire, Bytes) ->
  49. {ok, Socket} = gen_tcp:connect(Host, Port, [binary, {active, false}]),
  50. Reply = process_set(Socket, set, Key, Flags, Expire, Bytes),
  51. gen_tcp:close(Socket),
  52. Reply;
  53. set({socket, Socket}, Key, Flags, Expire, Bytes) ->
  54. process_set(Socket, set, Key, Flags, Expire, Bytes).
  55. %%====================================================================
  56. %% @doc Associate Bytes with Key if Key isn't set already in memcached.
  57. %% @spec add(memcached_connection(), Key::memcached_key(), Bytes::any()) ->
  58. %% ok | {error, not_stored}
  59. %%-spec(add/3::(memcached_connection(), memcached_key(), any()) ->
  60. %% ok | {error, not_stored}).
  61. add({host, Host, port, Port}, Key, Bytes) ->
  62. add({host, Host, port, Port}, Key, 0, 0, Bytes);
  63. add({socket, Socket}, Key, Bytes) ->
  64. add({socket, Socket}, Key, 0, 0, Bytes).
  65. %% @doc Associate Bytes with Key using Flags and Expire options if Key isn't set already in memcached.
  66. %% @spec add(memcached_connection(), Key::memcached_key(), Flags::integer(), Expire::integer(), Bytes::any()) ->
  67. %% ok | {error, not_stored}
  68. %%-spec(add/5::(memcached_connection(), memcached_key(), integer(), integer(), any()) ->
  69. %% ok | {error, not_stored}).
  70. add({host, Host, port, Port}, Key, Flags, Expire, Bytes) ->
  71. {ok, Socket} = gen_tcp:connect(Host, Port, [binary, {active, false}]),
  72. Reply = process_set(Socket, add, Key, Flags, Expire, Bytes),
  73. gen_tcp:close(Socket),
  74. Reply;
  75. add({socket, Socket}, Key, Flags, Expire, Bytes) ->
  76. process_set(Socket, add, Key, Flags, Expire, Bytes).
  77. %%====================================================================
  78. %% @doc Associate Bytes with Key if Key is set already in memcached.
  79. %% @spec replace(memcached_connection(), Key::memcached_key(), Bytes::any()) ->
  80. %% ok | {error, not_stored}
  81. %%-spec(replace/3::(memcached_connection(), memcached_key(), any()) ->
  82. %% ok | {error, not_stored}).
  83. replace({host, Host, port, Port}, Key, Bytes) ->
  84. replace({host, Host, port, Port}, Key, 0, 0, Bytes);
  85. replace({socket, Socket}, Key, Bytes) ->
  86. replace({socket, Socket}, Key, 0, 0, Bytes).
  87. %% @doc Associate Bytes with Key using Flags and Expire options if Key is set already in memcached.
  88. %% @spec replace(memcached_connection(), Key::memcached_key(), Flags::integer(), Expire::integer(), Bytes::any()) ->
  89. %% ok | {error, not_stored}
  90. %%-spec(replace/5::(memcached_connection(), memcached_key(), integer(), integer(), any()) ->
  91. %% ok | {error, not_stored}).
  92. replace({host, Host, port, Port}, Key, Flags, Expire, Bytes) ->
  93. {ok, Socket} = gen_tcp:connect(Host, Port, [binary, {active, false}]),
  94. Reply = process_set(Socket, replace, Key, Flags, Expire, Bytes),
  95. gen_tcp:close(Socket),
  96. Reply;
  97. replace({socket, Socket}, Key, Flags, Expire, Bytes) ->
  98. process_set(Socket, replace, Key, Flags, Expire, Bytes).
  99. %%====================================================================
  100. %% @doc Return value associated with Key. Will automatically convert
  101. %% back to erlang terms. Key can be a single key or a list of
  102. %% keys.
  103. %% @spec get(memcached_connection(), Key::memcached_key() | [Key::memcached_key()]) ->
  104. %% [any()]
  105. %%-spec(get/2::(memcached_connection(), memcached_key() | [memcached_key()]) ->
  106. %% [any()]).
  107. get({host, Host, port, Port}, [Head|Tail]) when is_list(Head) ->
  108. {ok, Socket} = gen_tcp:connect(Host, Port, [binary, {active, false}]),
  109. Reply = process_get(Socket, [Head] ++ Tail),
  110. gen_tcp:close(Socket),
  111. Reply;
  112. get({host, Host, port, Port}, Key) ->
  113. {ok, Socket} = gen_tcp:connect(Host, Port, [binary, {active, false}]),
  114. Reply = process_get(Socket, [Key]),
  115. gen_tcp:close(Socket),
  116. Reply;
  117. get({socket, Socket}, [Head|Tail]) when is_list(Head) ->
  118. process_get(Socket, [Head] ++ Tail);
  119. get({socket, Socket}, Key) ->
  120. process_get(Socket, [Key]).
  121. %%====================================================================
  122. %% @doc Delete a key from memcached
  123. %% @spec delete(memcached_connection(), Key::memcached_key()) ->
  124. %% ok | {error, not_found}
  125. %%-spec(delete/2::(memcached_connection(), memcached_key()) ->
  126. %% ok | {error, not_found}).
  127. delete({host, Host, port, Port}, Key) ->
  128. delete({host, Host, port, Port}, Key, 0);
  129. delete({socket, Socket}, Key) ->
  130. delete({socket, Socket}, Key, 0).
  131. %% @doc Delete a key from memcached after Time seconds
  132. %% @spec delete(memcached_connection(), Key::memcached_key(), Time::integer()) ->
  133. %% ok | {error, not_found}
  134. %%-spec(delete/3::(memcached_connection(), memcached_key(), integer()) ->
  135. %% ok | {error, not_found}).
  136. delete({host, Host, port, Port}, Key, Time) ->
  137. {ok, Socket} = gen_tcp:connect(Host, Port, [binary, {active, false}]),
  138. Reply = process_delete(Socket, Key, Time),
  139. gen_tcp:close(Socket),
  140. Reply;
  141. delete({socket, Socket}, Key, Time) ->
  142. process_delete(Socket, Key, Time).
  143. %%====================================================================
  144. %% @doc Delete a key from memcached
  145. %% @spec stats(memcached_connection()) ->
  146. %% [string()]
  147. %%-spec(stats/1::(memcached_connection()) ->
  148. %% [string()]).
  149. stats({host, Host, port, Port}) ->
  150. {ok, Socket} = gen_tcp:connect(Host, Port, [binary, {active, false}]),
  151. Reply = process_stats(Socket),
  152. gen_tcp:close(Socket),
  153. Reply;
  154. stats({socket, Socket}) ->
  155. process_stats(Socket).
  156. %%--------------------------------------------------------------------
  157. %%% Internal functions
  158. %%--------------------------------------------------------------------
  159. to_list(Key) when is_atom(Key) ->
  160. atom_to_list(Key);
  161. to_list(Key) when is_binary(Key) ->
  162. binary_to_list(Key);
  163. to_list(Key) when is_list(Key) ->
  164. Key.
  165. fetch_more(Socket, Len, More) ->
  166. %% Read what we need to grab the data.
  167. {ok, <<Data/binary>>} = gen_tcp:recv(Socket, Len - size(More)),
  168. Combined = <<More/binary, Data/binary>>,
  169. if
  170. size(Combined) < Len ->
  171. {Bytes, Rest} = fetch_more(Socket, Len, Combined),
  172. {Bytes, Rest};
  173. true ->
  174. <<Bytes:Len/binary>> = Combined,
  175. %% Read anything left.
  176. {ok, <<Rest/binary>>} = gen_tcp:recv(Socket, 0),
  177. {Bytes, Rest}
  178. end.
  179. parse_responses(Socket, <<"\r\n", Data/binary>>, Acc) ->
  180. parse_responses(Socket, Data, Acc);
  181. parse_responses(Socket, <<"VALUE ", Data/binary>>, Acc) ->
  182. {ok, [MemcacheKey, _, Len], More} = io_lib:fread("~s ~u ~u\r\n", binary_to_list(Data)),
  183. if
  184. %% 5 is size(<<"\r\nEND\r\n">>)
  185. length(More) < (Len + 7) ->
  186. %% If we didnt' read all the data, fetch the rest
  187. {Bytes, Rest} = fetch_more(Socket, Len, list_to_binary(More)),
  188. parse_responses(Socket, Rest, Acc ++ [{MemcacheKey, b2t(Bytes)}]);
  189. true ->
  190. <<Bytes:Len/binary, Rest/binary>> = list_to_binary(More),
  191. parse_responses(Socket, Rest, Acc ++ [{MemcacheKey, b2t(Bytes)}])
  192. end;
  193. %% Parse the get response
  194. parse_responses(_Socket, <<"END\r\n", _Rest/binary>>, Acc) ->
  195. {ok,Acc};
  196. parse_responses(_Socket, _Unrecognized, _Acc) ->
  197. mismatch_error.
  198. b2t(Binary) ->
  199. try binary_to_term(Binary)
  200. catch
  201. _:_ -> Binary
  202. end.
  203. %% Send get and handle the response
  204. process_get(Socket, Keys) ->
  205. KeyList = [to_list(X) || X <- Keys],
  206. ok = gen_tcp:send(Socket, list_to_binary(["get ", string:join(KeyList, " "), "\r\n"])),
  207. {ok, <<Data/binary>>} = gen_tcp:recv(Socket, 0),
  208. parse_responses(Socket, Data, []).
  209. %% Send set and handle the response
  210. process_set(Socket, Operation, Key, Flags, Expire, Data) when not(is_binary(Data)) ->
  211. process_set(Socket, Operation, Key, Flags, Expire, term_to_binary(Data));
  212. process_set(Socket, Operation, Key, Flags, Expire, Bytes) ->
  213. Op = atom_to_list(Operation),
  214. K = to_list(Key),
  215. Len = size(Bytes),
  216. L = list_to_binary( io_lib:format("~s ~s ~p ~p ~p",
  217. [Op, K, Flags, Expire, Len])),
  218. Line = <<L/binary, "\r\n">>,
  219. ok = gen_tcp:send(Socket, Line),
  220. ok = gen_tcp:send(Socket, <<Bytes/binary, "\r\n">>),
  221. {ok, Response} = gen_tcp:recv(Socket, 0),
  222. case Response of
  223. <<"STORED\r\n">> ->
  224. ok;
  225. <<"NOT_STORED\r\n">> ->
  226. {error, not_stored}
  227. end.
  228. %% Send delete and handle the response
  229. process_delete(Socket, Key, Time) ->
  230. Line = list_to_binary(io_lib:format("delete ~s ~p\r\n",
  231. [to_list(Key), Time])),
  232. ok = gen_tcp:send(Socket, Line),
  233. {ok, Response} = gen_tcp:recv(Socket, 0),
  234. case Response of
  235. <<"DELETED\r\n">> ->
  236. ok;
  237. <<"NOT_FOUND\r\n">> ->
  238. {error, not_found}
  239. end.
  240. %% Send stats and handle the response
  241. process_stats(Socket) ->
  242. Line = <<"stats\r\n">>,
  243. ok = gen_tcp:send(Socket, Line),
  244. {ok, Response} = gen_tcp:recv(Socket, 0),
  245. string:tokens(binary_to_list(Response), "\r\n").