session.d 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. /**
  2. Cookie based session support.
  3. Copyright: © 2012-2013 Sönke Ludwig
  4. License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
  5. Authors: Jan Krüger, Sönke Ludwig, Ilya Shipunov
  6. */
  7. module vibe.http.session;
  8. import vibe.core.log;
  9. import vibe.crypto.cryptorand;
  10. import std.array;
  11. import std.base64;
  12. import std.traits : hasAliasing;
  13. import std.variant;
  14. //random number generator
  15. //TODO: Use Whirlpool or SHA-512 here
  16. private SHA1HashMixerRNG g_rng;
  17. //The "URL and Filename safe" Base64 without padding
  18. alias Base64URLNoPadding = Base64Impl!('-', '_', Base64.NoPadding);
  19. /**
  20. Represents a single HTTP session.
  21. Indexing the session object with string keys allows to store arbitrary key/value pairs.
  22. */
  23. struct Session {
  24. private {
  25. SessionStore m_store;
  26. string m_id;
  27. SessionStorageType m_storageType;
  28. }
  29. // created by the SessionStore using SessionStore.createSessionInstance
  30. private this(SessionStore store, string id = null)
  31. @safe {
  32. assert(id.length > 0);
  33. m_store = store;
  34. m_id = id;
  35. m_storageType = store.storageType;
  36. }
  37. /** Checks if the session is active.
  38. This operator enables a $(D Session) value to be used in conditionals
  39. to check if they are actially valid/active.
  40. */
  41. bool opCast() const @safe { return m_store !is null; }
  42. ///
  43. unittest {
  44. //import vibe.http.server;
  45. // workaround for cyclic module ctor compiler error
  46. class HTTPServerRequest { Session session; string[string] form; }
  47. class HTTPServerResponse { Session startSession() { assert(false); } }
  48. void login(scope HTTPServerRequest req, scope HTTPServerResponse res)
  49. {
  50. // TODO: validate username+password
  51. // ensure that there is an active session
  52. if (!req.session) req.session = res.startSession();
  53. // update session variables
  54. req.session.set("loginUser", req.form["user"]);
  55. }
  56. }
  57. /// Returns the unique session id of this session.
  58. @property string id() const @safe { return m_id; }
  59. /// Queries the session for the existence of a particular key.
  60. bool isKeySet(string key) @safe { return m_store.isKeySet(m_id, key); }
  61. /** Gets a typed field from the session.
  62. */
  63. const(T) get(T)(string key, lazy T def_value = T.init)
  64. @trusted { // Variant, deserializeJson/deserializeBson
  65. static assert(!hasAliasing!T, "Type "~T.stringof~" contains references, which is not supported for session storage.");
  66. auto val = m_store.get(m_id, key, serialize(def_value));
  67. return deserialize!T(val);
  68. }
  69. /** Sets a typed field to the session.
  70. */
  71. void set(T)(string key, T value)
  72. {
  73. static assert(!hasAliasing!T, "Type "~T.stringof~" contains references, which is not supported for session storage.");
  74. m_store.set(m_id, key, serialize(value));
  75. }
  76. // Removes a field from a session
  77. void remove(string key) @safe { m_store.remove(m_id, key); }
  78. /**
  79. Enables foreach-iteration over all keys of the session.
  80. */
  81. int opApply(scope int delegate(string key) @safe del)
  82. @safe {
  83. return m_store.iterateSession(m_id, del);
  84. }
  85. ///
  86. unittest {
  87. //import vibe.http.server;
  88. // workaround for cyclic module ctor compiler error
  89. class HTTPServerRequest { Session session; }
  90. class HTTPServerResponse { import vibe.core.stream; OutputStream bodyWriter() @safe { assert(false); } string contentType; }
  91. // sends all session entries to the requesting browser
  92. // assumes that all entries are strings
  93. void handleRequest(scope HTTPServerRequest req, scope HTTPServerResponse res)
  94. {
  95. res.contentType = "text/plain";
  96. req.session.opApply((key) @safe {
  97. res.bodyWriter.write(key ~ ": " ~ req.session.get!string(key) ~ "\n");
  98. return 0;
  99. });
  100. }
  101. }
  102. package void destroy() @safe { m_store.destroy(m_id); }
  103. private Variant serialize(T)(T val)
  104. {
  105. import vibe.data.json;
  106. import vibe.data.bson;
  107. final switch (m_storageType) with (SessionStorageType) {
  108. case native: return () @trusted { return Variant(val); } ();
  109. case json: return () @trusted { return Variant(serializeToJson(val)); } ();
  110. case bson: return () @trusted { return Variant(serializeToBson(val)); } ();
  111. }
  112. }
  113. private T deserialize(T)(ref Variant val)
  114. {
  115. import vibe.data.json;
  116. import vibe.data.bson;
  117. final switch (m_storageType) with (SessionStorageType) {
  118. case native: return () @trusted { return val.get!T; } ();
  119. case json: return () @trusted { return deserializeJson!T(val.get!Json); } ();
  120. case bson: return () @trusted { return deserializeBson!T(val.get!Bson); } ();
  121. }
  122. }
  123. }
  124. /**
  125. Interface for a basic session store.
  126. A session store is responsible for storing the id and the associated key/value pairs of a
  127. session.
  128. */
  129. interface SessionStore {
  130. @safe:
  131. /// Returns the internal type used for storing session keys.
  132. @property SessionStorageType storageType() const;
  133. /// Creates a new session.
  134. Session create();
  135. /// Opens an existing session.
  136. Session open(string id);
  137. /// Sets a name/value pair for a given session.
  138. void set(string id, string name, Variant value);
  139. /// Returns the value for a given session key.
  140. Variant get(string id, string name, lazy Variant defaultVal);
  141. /// Determines if a certain session key is set.
  142. bool isKeySet(string id, string key);
  143. /// Removes a key from a session
  144. void remove(string id, string key);
  145. /// Terminates the given session.
  146. void destroy(string id);
  147. /// Iterates all keys stored in the given session.
  148. int iterateSession(string id, scope int delegate(string key) @safe del);
  149. /// Creates a new Session object which sources its contents from this store.
  150. protected final Session createSessionInstance(string id = null)
  151. {
  152. if (!id.length) {
  153. ubyte[64] rand;
  154. if (!g_rng) g_rng = new SHA1HashMixerRNG();
  155. g_rng.read(rand);
  156. id = () @trusted { return cast(immutable)Base64URLNoPadding.encode(rand); } ();
  157. }
  158. return Session(this, id);
  159. }
  160. }
  161. enum SessionStorageType {
  162. native,
  163. json,
  164. bson
  165. }
  166. /**
  167. Session store for storing a session in local memory.
  168. If the server is running as a single instance (no thread or process clustering), this kind of
  169. session store provies the fastest and simplest way to store sessions. In any other case,
  170. a persistent session store based on a database is necessary.
  171. */
  172. final class MemorySessionStore : SessionStore {
  173. @safe:
  174. private {
  175. Variant[string][string] m_sessions;
  176. }
  177. @property SessionStorageType storageType()
  178. const {
  179. return SessionStorageType.native;
  180. }
  181. Session create()
  182. {
  183. auto s = createSessionInstance();
  184. m_sessions[s.id] = null;
  185. return s;
  186. }
  187. Session open(string id)
  188. {
  189. auto pv = id in m_sessions;
  190. return pv ? createSessionInstance(id) : Session.init;
  191. }
  192. void set(string id, string name, Variant value)
  193. @trusted { // Variant
  194. m_sessions[id][name] = value;
  195. foreach(k, v; m_sessions[id]) logTrace("Csession[%s][%s] = %s", id, k, v);
  196. }
  197. Variant get(string id, string name, lazy Variant defaultVal)
  198. @trusted { // Variant
  199. assert(id in m_sessions, "session not in store");
  200. foreach(k, v; m_sessions[id]) logTrace("Dsession[%s][%s] = %s", id, k, v);
  201. if (auto pv = name in m_sessions[id]) {
  202. return *pv;
  203. } else {
  204. return defaultVal;
  205. }
  206. }
  207. bool isKeySet(string id, string key)
  208. {
  209. return (key in m_sessions[id]) !is null;
  210. }
  211. void remove(string id, string key)
  212. {
  213. m_sessions[id].remove(key);
  214. }
  215. void destroy(string id)
  216. {
  217. m_sessions.remove(id);
  218. }
  219. int delegate(int delegate(ref string key, ref Variant value) @safe) @safe iterateSession(string id)
  220. {
  221. assert(id in m_sessions, "session not in store");
  222. int iterator(int delegate(ref string key, ref Variant value) @safe del)
  223. @safe {
  224. foreach( key, ref value; m_sessions[id] )
  225. if( auto ret = del(key, value) != 0 )
  226. return ret;
  227. return 0;
  228. }
  229. return &iterator;
  230. }
  231. int iterateSession(string id, scope int delegate(string key) @safe del)
  232. @trusted { // hash map iteration
  233. assert(id in m_sessions, "session not in store");
  234. foreach (key; m_sessions[id].byKey)
  235. if (auto ret = del(key))
  236. return ret;
  237. return 0;
  238. }
  239. }