|  | // Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  |  | 
|  | part of dart._http; | 
|  |  | 
|  | const String _DART_SESSION_ID = "DARTSESSID"; | 
|  |  | 
|  | // A _HttpSession is a node in a double-linked list, with _next and _prev being | 
|  | // the previous and next pointers. | 
|  | class _HttpSession implements HttpSession { | 
|  | // Destroyed marked. Used by the http connection to see if a session is valid. | 
|  | bool _destroyed = false; | 
|  | bool _isNew = true; | 
|  | DateTime _lastSeen; | 
|  | void Function()? _timeoutCallback; | 
|  | final _HttpSessionManager _sessionManager; | 
|  | // Pointers in timeout queue. | 
|  | _HttpSession? _prev; | 
|  | _HttpSession? _next; | 
|  | final String id; | 
|  |  | 
|  | final Map _data = HashMap(); | 
|  |  | 
|  | _HttpSession(this._sessionManager, this.id) : _lastSeen = DateTime.now(); | 
|  |  | 
|  | void destroy() { | 
|  | assert(!_destroyed); | 
|  | _destroyed = true; | 
|  | _sessionManager._removeFromTimeoutQueue(this); | 
|  | _sessionManager._sessions.remove(id); | 
|  | } | 
|  |  | 
|  | // Mark the session as seen. This will reset the timeout and move the node to | 
|  | // the end of the timeout queue. | 
|  | void _markSeen() { | 
|  | _lastSeen = DateTime.now(); | 
|  | _sessionManager._bumpToEnd(this); | 
|  | } | 
|  |  | 
|  | DateTime get lastSeen => _lastSeen; | 
|  |  | 
|  | bool get isNew => _isNew; | 
|  |  | 
|  | void set onTimeout(void Function()? callback) { | 
|  | _timeoutCallback = callback; | 
|  | } | 
|  |  | 
|  | // Map implementation: | 
|  | bool containsValue(value) => _data.containsValue(value); | 
|  | bool containsKey(key) => _data.containsKey(key); | 
|  | operator [](key) => _data[key]; | 
|  | void operator []=(key, value) { | 
|  | _data[key] = value; | 
|  | } | 
|  |  | 
|  | putIfAbsent(key, ifAbsent) => _data.putIfAbsent(key, ifAbsent); | 
|  | addAll(Map other) => _data.addAll(other); | 
|  | remove(key) => _data.remove(key); | 
|  | void clear() { | 
|  | _data.clear(); | 
|  | } | 
|  |  | 
|  | void forEach(void f(key, value)) { | 
|  | _data.forEach(f); | 
|  | } | 
|  |  | 
|  | Iterable<MapEntry> get entries => _data.entries; | 
|  |  | 
|  | void addEntries(Iterable<MapEntry> entries) { | 
|  | _data.addEntries(entries); | 
|  | } | 
|  |  | 
|  | Map<K, V> map<K, V>(MapEntry<K, V> transform(key, value)) => | 
|  | _data.map(transform); | 
|  |  | 
|  | void removeWhere(bool test(key, value)) { | 
|  | _data.removeWhere(test); | 
|  | } | 
|  |  | 
|  | Map<K, V> cast<K, V>() => _data.cast<K, V>(); | 
|  | update(key, update(value), {Function()? ifAbsent}) => | 
|  | _data.update(key, update, ifAbsent: ifAbsent); | 
|  |  | 
|  | void updateAll(update(key, value)) { | 
|  | _data.updateAll(update); | 
|  | } | 
|  |  | 
|  | Iterable get keys => _data.keys; | 
|  | Iterable get values => _data.values; | 
|  | int get length => _data.length; | 
|  | bool get isEmpty => _data.isEmpty; | 
|  | bool get isNotEmpty => _data.isNotEmpty; | 
|  |  | 
|  | String toString() => 'HttpSession id:$id $_data'; | 
|  | } | 
|  |  | 
|  | // Private class used to manage all the active sessions. The sessions are stored | 
|  | // in two ways: | 
|  | // | 
|  | //  * In a map, mapping from ID to HttpSession. | 
|  | //  * In a linked list, used as a timeout queue. | 
|  | class _HttpSessionManager { | 
|  | final Map<String, _HttpSession> _sessions; | 
|  | int _sessionTimeout = 20 * 60; // 20 mins. | 
|  | _HttpSession? _head; | 
|  | _HttpSession? _tail; | 
|  | Timer? _timer; | 
|  |  | 
|  | _HttpSessionManager() : _sessions = {}; | 
|  |  | 
|  | String createSessionId() { | 
|  | const int _KEY_LENGTH = 16; // 128 bits. | 
|  | var data = _CryptoUtils.getRandomBytes(_KEY_LENGTH); | 
|  | return _CryptoUtils.bytesToHex(data); | 
|  | } | 
|  |  | 
|  | _HttpSession? getSession(String id) => _sessions[id]; | 
|  |  | 
|  | _HttpSession createSession() { | 
|  | var id = createSessionId(); | 
|  | // TODO(ajohnsen): Consider adding a limit and throwing an exception. | 
|  | // Should be very unlikely however. | 
|  | while (_sessions.containsKey(id)) { | 
|  | id = createSessionId(); | 
|  | } | 
|  | var session = _sessions[id] = _HttpSession(this, id); | 
|  | _addToTimeoutQueue(session); | 
|  | return session; | 
|  | } | 
|  |  | 
|  | void set sessionTimeout(int timeout) { | 
|  | _sessionTimeout = timeout; | 
|  | _stopTimer(); | 
|  | _startTimer(); | 
|  | } | 
|  |  | 
|  | void close() { | 
|  | _stopTimer(); | 
|  | } | 
|  |  | 
|  | void _bumpToEnd(_HttpSession session) { | 
|  | _removeFromTimeoutQueue(session); | 
|  | _addToTimeoutQueue(session); | 
|  | } | 
|  |  | 
|  | void _addToTimeoutQueue(_HttpSession session) { | 
|  | if (_head == null) { | 
|  | assert(_tail == null); | 
|  | _tail = _head = session; | 
|  | _startTimer(); | 
|  | } else { | 
|  | assert(_timer != null); | 
|  | var tail = _tail!; | 
|  | // Add to end. | 
|  | tail._next = session; | 
|  | session._prev = tail; | 
|  | _tail = session; | 
|  | } | 
|  | } | 
|  |  | 
|  | void _removeFromTimeoutQueue(_HttpSession session) { | 
|  | var next = session._next; | 
|  | var prev = session._prev; | 
|  | session._next = session._prev = null; | 
|  | next?._prev = prev; | 
|  | prev?._next = next; | 
|  | if (_tail == session) { | 
|  | _tail = prev; | 
|  | } | 
|  | if (_head == session) { | 
|  | _head = next; | 
|  | // We removed the head element, start new timer. | 
|  | _stopTimer(); | 
|  | _startTimer(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void _timerTimeout() { | 
|  | _stopTimer(); // Clear timer. | 
|  | var session = _head!; | 
|  | session.destroy(); // Will remove the session from timeout queue and map. | 
|  | session._timeoutCallback?.call(); | 
|  | } | 
|  |  | 
|  | void _startTimer() { | 
|  | assert(_timer == null); | 
|  | var head = _head; | 
|  | if (head != null) { | 
|  | int seconds = DateTime.now().difference(head.lastSeen).inSeconds; | 
|  | _timer = Timer( | 
|  | Duration(seconds: _sessionTimeout - seconds), | 
|  | _timerTimeout, | 
|  | ); | 
|  | } | 
|  | } | 
|  |  | 
|  | void _stopTimer() { | 
|  | var timer = _timer; | 
|  | if (timer != null) { | 
|  | timer.cancel(); | 
|  | _timer = null; | 
|  | } | 
|  | } | 
|  | } |