Last active
October 6, 2015 05:07
-
-
Save mitsuoka/2941231 to your computer and use it in GitHub Desktop.
Cookie based HTTP session manager library and its sample applications
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
*** Cookie based HTTP session manager library *** | |
This is a third party session manager developed before the HttpSession was added | |
to dart:io on October 30th. See: http://code.google.com/p/dart/issues/detail?id=3258. | |
Functions and methods in this library are almost equivalent to those of Java Sevlet. | |
Note: Under evaluation. Do not use this code for actual applications. | |
Applications should not use response.headers.set("Set-Cookie", someString); | |
Instard, use the setCookieParameter method. | |
Available functions and methods: | |
Session getSession(HttpRequest request, HttpResponse response) | |
String getRequestedSessionId(HttpRequest request) | |
bool isRequestedSessionIdValid(HttpRequest request) | |
bool Session#isNew() | |
void Session#invalidate() | |
String Session#getId() | |
int Session#getCreationTime() | |
int Session#getLastAccessedTime() | |
void Session#setMaxInactiveInterval(int t) | |
int Session#getMaxInactiveInterval() | |
Map Session#getAttributes() | |
dynamic Session#getAttribute(String name) | |
void Session#setAttribute(String name, Dynamic value) | |
void Session#removeAttribute(String name) | |
void setCookieParameter(HttpResponse response, String name, String value, [String path = null]) | |
Map getCookieParameters(HttpRequest request) | |
Ref: www.cresc.co.jp/tech/java/Google_Dart/DartLanguageGuide.pdf (in Japanese) | |
May 2012, by Cresc Corp. | |
June and July 2012, modified to incorporate API (Date and Timer) changes | |
September 2012, incorporated API (Clock and Random) | |
October 2012, modified to avoid conflict between newly added HttpSession abstract class | |
and incorporated M1 changes | |
February 2013, API change (Date -> DateTime) incorporated | |
February 2013, API changes (dart:io) incorporated | |
March 2013, API changes (String and Timer) incorporated | |
*/ | |
library HttpSessionManager; | |
import "dart:async"; | |
import "dart:uri" as uri; | |
import "dart:io"; | |
import "dart:isolate"; // Timer interface moved to the "dart:isolate" (June 22, 2012) | |
import "dart:math" as Math; | |
// *** Top level session table and constants *** | |
Map<String, Map> _sessions; // session table | |
final int _defaultMaxInactiveInterval = 1800; // 30 minutes default timeout | |
final int _sessionGarbageCollectorTick = 300; // repeat every 5 minutes | |
// *** Top level functions *** | |
// getSession | |
Session getSession(HttpRequest request, HttpResponse response) { | |
if (_sessions == null) { | |
_sessions = new Map<String, Map>(); | |
sessionGarbageCollect(); | |
}; | |
var id = getRequestedSessionId(request); | |
if (id == null) { | |
return new Session.fresh(request, response); | |
} else if (_sessions[id] == null) { | |
return new Session.fresh(request, response); | |
} else if (_sessions[id]["invalidated"] == true) { | |
_sessions.remove(id); | |
return new Session.fresh(request, response); | |
} | |
else { // session exist | |
Session session = new Session(); | |
session._sessionId = id; | |
session._attributes = _sessions[id]["attributes"]; | |
DateTime lastAccessedTime =_sessions[id]["lastAccessedTime"]; | |
int maxInactiveInterval = _sessions[id]["maxInactiveInterval"]; | |
_sessions[id].remove("lastAccessedTime"); | |
_sessions[id]["lastAccessedTime"] = new DateTime.now(); | |
_sessions[id].remove("isNew"); | |
_sessions[id]["isNew"] = false; | |
if (maxInactiveInterval < 0) { return session; | |
} else if (new DateTime.now().millisecondsSinceEpoch > lastAccessedTime.millisecondsSinceEpoch + maxInactiveInterval * 1000){ | |
_sessions.remove(id); // session expired | |
session = new Session.fresh(request, response); | |
} | |
return session; | |
} | |
} | |
// Get session ID from the request. | |
String getRequestedSessionId(HttpRequest request) { | |
if (getCookieParameters(request) == null) { return null; | |
} else { return getCookieParameters(request)["DSESSIONID"]; | |
} | |
} | |
// isRequestedSessionIdValid(HttpRequest request) | |
bool isRequestedSessionIdValid(HttpRequest request) { | |
var id = getRequestedSessionId(request); | |
if (id == null) { return false; | |
} else if (_sessions.containsKey(id) == false) { return false; | |
} else if (_sessions[id]["invalidated"] == true) { return false; | |
} else if (_sessions[id]["lastAccessedTime"].millisecondsSinceEpoch + _sessions[id]["maxInactiveInterval"] | |
* 1000 > new DateTime.now().millisecondsSinceEpoch) { | |
return true; | |
} | |
else { return false; | |
} | |
} | |
// Set cookie parameter to the response header. | |
// (Name and value will be URI encoded.) | |
void setCookieParameter(HttpResponse response, String name, String value, [String path = null]) { | |
if (path == null) { | |
response.headers.add("Set-Cookie", | |
"${uri.encodeUriComponent(name)}=${uri.encodeUriComponent(value)}"); | |
} | |
else { response.headers.add("Set-Cookie", | |
"${uri.encodeUriComponent(name)}=${uri.encodeUriComponent(value)};Path=${path}"); | |
} | |
} | |
// Get cookie parameters from the request | |
Map getCookieParameters(HttpRequest request) { | |
String cookieHeader = request.headers.value("Cookie"); | |
if (cookieHeader == null) return null; // no Session header included | |
return _splitHeaderString(cookieHeader); | |
} | |
// Session garbage collection (modify this to run at midnight) | |
void sessionGarbageCollect() { | |
print("${new DateTime.now()} sessionGarbageCollector - started"); | |
void collect(Timer t) { | |
List removeKeys = []; | |
int now = new DateTime.now().millisecondsSinceEpoch; | |
_sessions.forEach((key, value){ | |
if (key != "" && _sessions[key]["lastAccessedTime"].millisecondsSinceEpoch + _sessions[key]["maxInactiveInterval"] * 1000 < now) { | |
removeKeys.add(key); | |
print("${new DateTime.now()} sessionGarbageCollector - removed session $key"); | |
} | |
}); | |
for (String key in removeKeys) _sessions.remove(key); | |
} | |
new Timer.periodic(new Duration(seconds: _sessionGarbageCollectorTick), collect); | |
} | |
/* | |
*** HttpSession class *** | |
*/ | |
class Session { | |
String _sessionId; | |
Map<String, dynamic> _attributes; | |
// Construct base session object | |
Session(){} | |
// Construct new session object | |
Session.fresh(HttpRequest request, HttpResponse response) { | |
_attributes = new Map<String, dynamic>(); | |
_sessionId = _createSessionId(); | |
response.headers.add("Set-Cookie", " DSESSIONID = $_sessionId; Path = ${request.uri.path}; HttpOnly"); | |
_sessions[_sessionId] = {"invalidated": false, | |
"isNew": true, | |
"creationTime": new DateTime.now(), | |
"lastAccessedTime": new DateTime.now(), | |
"maxInactiveInterval": _defaultMaxInactiveInterval, | |
"attributes": _attributes}; | |
} | |
// isNew() | |
bool isNew() => _sessions[_sessionId]["isNew"]; | |
// invalidate() : this session will be deleted at the next request | |
void invalidate() { | |
_sessions[_sessionId]["invalidated"] = true; | |
_sessions[_sessionId]["attributes"] = new Map<String, dynamic>(); | |
} | |
// getId() | |
String getId() => _sessionId; | |
// getCreationTime() | |
DateTime getCreationTime() => _sessions[_sessionId]["creationTime"]; | |
// getLastAccessedTime() | |
DateTime getLastAccessedTime() => _sessions[_sessionId]["lastAccessedTime"]; | |
// setMaxInactiveInterval() : set -1 to use default timeout value | |
void setMaxInactiveInterval(int t) { | |
if (t < 0) t = _defaultMaxInactiveInterval; | |
_sessions[_sessionId].remove("maxInactiveInterval"); | |
_sessions[_sessionId]["maxInactiveInterval"] = t; | |
} | |
// getMaxInactiveInterval() | |
int getMaxInactiveInterval() => _sessions[_sessionId]["maxInactiveInterval"]; | |
// getAttributes() | |
Map getAttributes(){ | |
return _attributes; | |
} | |
// getAttribute(String name) | |
dynamic getAttribute(String name) { | |
if (_attributes.containsKey(name)) { | |
return _attributes[name]; | |
} | |
else { return null; | |
} | |
} | |
// setAttribute(String name, Dynamic value) | |
void setAttribute(String name, dynamic value) { | |
_attributes.remove(name); | |
_attributes[name] = value; | |
_sessions[_sessionId].remove("attributes"); | |
_sessions[_sessionId]["attributes"] = _attributes; | |
} | |
// getAttributeNames() | |
List getAttributeNames() { | |
List<String> attNames = []; | |
for(String x in _attributes.keys){ | |
attNames.add(x); | |
} | |
return attNames; | |
} | |
// removeAttribute() | |
void removeAttribute(String name) { | |
_attributes.remove(name); | |
_sessions[_sessionId].remove("attributes"); | |
_sessions[_sessionId]["attributes"] = _attributes; | |
} | |
} | |
/* | |
*** Utilities *** | |
*/ | |
// Split cookie header string. | |
// "," separation is used for cookies in a single set-cookie folded header | |
// ";" separation is used for cookies sent by multiple set-cookie headers | |
Map<String, String> _splitHeaderString(String cookieString) { | |
Map<String, String> result = new Map<String, String>(); | |
int currentPosition = 0; | |
int position0; | |
int position1; | |
int position2; | |
while (currentPosition < cookieString.length) { | |
int position = cookieString.indexOf("=", currentPosition); | |
if (position == -1) { | |
break; | |
} | |
String name = cookieString.substring(currentPosition, position); | |
currentPosition = position + 1; | |
position1 = cookieString.indexOf(";", currentPosition); | |
position2 = cookieString.indexOf(",", currentPosition); | |
String value; | |
if (position1 == -1 && position2 == -1) { | |
value = cookieString.substring(currentPosition); | |
currentPosition = cookieString.length; | |
} else { | |
if (position1 == -1) { position0 = position2; | |
} else if (position2 == -1) { position0 = position1; | |
} else if (position1 < position2) { position0 = position1; | |
} else { position0 = position2; | |
} | |
value = cookieString.substring(currentPosition, position0); | |
currentPosition = position0 + 1; | |
} | |
result[uri.decodeUriComponent(name.trim())] = uri.decodeUriComponent(value.trim()); | |
} | |
return result; | |
} | |
// Create a new session ID. | |
// Note: This is a sample, don't use in real applications. | |
String _createSessionId() { | |
String rndHash = _createHash(new Math.Random().nextInt(0xFFFFFFFF)); | |
String dateHash = _createHash(new DateTime.now().millisecondsSinceEpoch & 0xFFFFFFFF); | |
return "${rndHash}${dateHash}"; | |
} | |
// Create hash hexa string from int value. | |
String _createHash(int iv) { | |
List bytes = []; | |
for (int i = 0; i < 4; i++){ | |
bytes.add(iv & 0xff); | |
iv = iv >> 8; | |
} | |
var hexaHash = ""; | |
int intHash = _getHash(bytes); | |
for (int i = 0; i < 8; i++){ | |
hexaHash = (intHash & 0xf).toRadixString(16) + hexaHash; | |
intHash = intHash >> 4; | |
} | |
return hexaHash; | |
} | |
// Fowler/Noll/Vo (FNV) 32-bit hash function. | |
int _getHash(List<int> bytes) { | |
int fnv_prime = 0x811C9DC5; | |
int hash = 0; | |
for(int i = 0; i < bytes.length; i++) | |
{ | |
hash *= fnv_prime; | |
hash ^= bytes[i]; | |
} | |
return hash & 0xFFFFFFFF; | |
} | |
// Create session log | |
StringBuffer createSessionLog(Session session, HttpRequest request) { | |
var sb = new StringBuffer(""); | |
if (session == null) { sb.write("HttpSession data : null"); | |
} else if (session.getId() == null) { sb.write("HttpSession data : null"); | |
} else { sb.write('''HttpSession related data: | |
number of existing sessions : ${_sessions.length} | |
getCookieParameters : ${getCookieParameters(request)} | |
getRequestedSessionId : ${getRequestedSessionId(request)} | |
isRequestedSessionIdValid : ${isRequestedSessionIdValid(request)} | |
session.isNew : ${session.isNew()} | |
session.getId : ${session.getId()} | |
session.getCreationTime : ${session.getCreationTime()} | |
session.getLastAccessedTime : ${session.getLastAccessedTime()} | |
session.getMaxInactiveInterval : ${session.getMaxInactiveInterval()} Seconds | |
session.getAttributeNames : ${session.getAttributeNames()} | |
session.getAttributes : ${session.getAttributes()} | |
'''); | |
} | |
return sb; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Dart code sample : Simple HttpSessionManager test server. | |
Note: Do not use this code for actual applications. | |
Usage: | |
1) Run this HttpSessionTestServer.dart as server. | |
2) Access this server from your browser : http://localhost:8080/SessionTest | |
Ref: www.cresc.co.jp/tech/java/Google_Dart/DartLanguageGuide.pdf (in Japanese) | |
May 2012, by Cresc Corp. | |
October 2012, incorporated M1 changes | |
January 2013, incorporated API changes | |
February 2013, incorporated API cange (Date -> DateTime) | |
February 2013, API changes (dart:io) incorporated | |
March 2013, API changes (HttpResponse) incorporated | |
*/ | |
import "dart:io"; | |
import "dart:utf" as utf; | |
import "HttpSessionManager.dart" as slib; | |
final HOST = "127.0.0.1"; | |
final PORT = 8080; | |
final REQUEST_PATH = "/SessionTest"; | |
final LOG_REQUESTS = false; | |
final int MaxInactiveInterval = 20; // set this parameter in seconds. Set -1 to disable timeout. | |
void main() { | |
HttpServer.bind(HOST, PORT) | |
.then((HttpServer server) { | |
server.listen( | |
(HttpRequest request) { | |
if (request.uri.path == REQUEST_PATH) { | |
requestReceivedHandler(request); | |
} | |
}); | |
print("${new DateTime.now()} Serving $REQUEST_PATH on http://${HOST}:${PORT}."); | |
}); | |
} | |
void requestReceivedHandler(HttpRequest request) { | |
HttpResponse response = request.response; | |
if (LOG_REQUESTS) print(createLogMessage(request).toString()); | |
var session = slib.getSession(request, response); | |
if (session != null){ | |
if (session.isNew()) session.setMaxInactiveInterval(MaxInactiveInterval); | |
} | |
if (LOG_REQUESTS) print(slib.createSessionLog(session, request).toString()); | |
response.headers.add("Content-Type", "text/html; charset=UTF-8"); | |
// cookie setting example (accepts multi-byte characters) | |
slib.setCookieParameter(response, "testName", "TestValue_\u221A2=1.41", request.uri.path); | |
response.write(createHtmlResponse(request, session)); | |
response.close(); | |
} | |
String createHtmlResponse(HttpRequest request, slib.Session session) { | |
if (request.queryParameters["command"] == "Finish" || request.queryParameters["command"] == null || slib.isRequestedSessionIdValid(request) == false) { | |
return ''' | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>HttpSessionTest</title> | |
</head> | |
<body> | |
<h1>Initial Page</h1><br><br><br> | |
<form method="get" action="/SessionTest"> | |
<input type="submit" name="command" value="Start"> | |
</form><br> | |
<pre>${makeSafe(createLogMessage(request)).toString()}</pre><br> | |
<pre>${makeSafe(slib.createSessionLog(session, request)).toString()}</pre> | |
</body> | |
</html>'''; | |
} | |
int pageNumber; | |
if (session.isNew() || request.queryParameters["command"] == "Start") { pageNumber = 1; | |
} else if (request.queryParameters["command"] == "Next Page") { | |
pageNumber = session.getAttribute("pageNumber") + 1; | |
// pageNumber = Math.parseInt(session.getAttribute("pageNumber").toString()) + 1; | |
} | |
session.setAttribute("pageNumber", pageNumber); | |
return ''' | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>HttpSessionTest</title> | |
</head> | |
<body> | |
<h1>Page ${pageNumber}</h1><br> | |
Session will be expired after ${session.getMaxInactiveInterval()} seconds. | |
(set minus value to suspend timeout)<br><br> | |
<form method="get" action="/SessionTest"> | |
<input type="submit" name="command" value="Next Page"> | |
<input type="submit" name="command" value="Finish"> | |
</form><br> | |
<pre>${makeSafe(createLogMessage(request)).toString()}</pre><br> | |
<pre>${makeSafe(slib.createSessionLog(session, request)).toString()}</pre> | |
</body> | |
</html>'''; | |
} | |
// create log message | |
StringBuffer createLogMessage(HttpRequest request, {String bodyString}) { | |
var sb = new StringBuffer( '''Request related data: | |
request.method : ${request.method} | |
request.path : ${request.uri.path} | |
request.uri : ${request.uri} | |
request.queryString : ${request.uri.query} | |
request.queryParameters : | |
'''); | |
request.queryParameters.forEach((key, value){ | |
sb.write(" ${key} : ${value}\n"); | |
}); | |
sb.write(''' request.cookies : | |
'''); | |
request.cookies.forEach((value){ | |
sb.write(" ${value.toString()}\n"); | |
}); | |
sb.write(''' request.headers.expires : ${request.headers.expires} | |
request.headers.host : ${request.headers.host} | |
request.headers.port : ${request.headers.port} | |
request.headers : | |
'''); | |
var str = request.headers.toString(); | |
for (int i = 0; i < str.length - 1; i++){ | |
if (str[i] == "\n") { sb.write("\n "); | |
} else { sb.write(str[i]); | |
} | |
} | |
if (request.method == "POST") { | |
var enctype = request.headers["content-type"]; | |
if (enctype[0].contains("text")) { | |
sb.write("\nrequest body string :\n ${bodyString.replaceAll('+', ' ')}"); | |
} else if (enctype[0].contains("urlencoded")) { | |
sb.write("\nrequest body string (URL decoded):\n ${urlDecode(bodyString)}"); | |
} | |
} | |
sb.write("\n"); | |
return sb; | |
} | |
// make safe string buffer data as HTML text | |
StringBuffer makeSafe(StringBuffer b) { | |
var s = b.toString(); | |
b = new StringBuffer(); | |
for (int i = 0; i < s.length; i++){ | |
if (s[i] == '&') { b.write('&'); | |
} else if (s[i] == '"') { b.write('"'); | |
} else if (s[i] == "'") { b.write('''); | |
} else if (s[i] == '<') { b.write('<'); | |
} else if (s[i] == '>') { b.write('>'); | |
} else { b.write(s[i]); | |
} | |
} | |
return b; | |
} | |
// URL decoder decodes url encoded utf-8 bytes | |
// Use this method to decode query string | |
// We need this kind of encoder and decoder with optional [encType] argument | |
String urlDecode(String s){ | |
int i, p, q; | |
var ol = new List<int>(); | |
for (i = 0; i < s.length; i++) { | |
if (s[i].codeUnitAt(0) == 0x2b) { ol.add(0x20); // convert + to space | |
} else if (s[i].codeUnitAt(0) == 0x25) { // convert hex bytes to a single bite | |
i++; | |
p = s[i].toUpperCase().codeUnitAt(0) - 0x30; | |
if (p > 9) p = p - 7; | |
i++; | |
q = s[i].toUpperCase().codeUnitAt(0) - 0x30; | |
if (q > 9) q = q - 7; | |
ol.add(p * 16 + q); | |
} | |
else { ol.add(s[i].codeUnitAt(0)); | |
} | |
} | |
return utf.decodeUtf8(ol); | |
} | |
// URL encoder encodes string into url encoded utf-8 bytes | |
// Use this method to encode cookie string | |
// or to write URL encoded byte data into OutputStream | |
List<int> urlEncode(String s) { | |
int i, p, q; | |
var ol = new List<int>(); | |
List<int> il = utf.encodeUtf8(s); | |
for (i = 0; i < il.length; i++) { | |
if (il[i] == 0x20) { ol.add(0x2b); // convert sp to + | |
} else if (il[i] == 0x2a || il[i] == 0x2d || il[i] == 0x2e || il[i] == 0x5f) { ol.add(il[i]); // do not convert | |
} else if (((il[i] >= 0x30) && (il[i] <= 0x39)) || ((il[i] >= 0x41) && (il[i] <= 0x5a)) || ((il[i] >= 0x61) && (il[i] <= 0x7a))) { ol.add(il[i]); | |
} else { // '%' shift | |
ol.add(0x25); | |
ol.add((il[i] ~/ 0x10).toRadixString(16).codeUnitAt(0)); | |
ol.add((il[i] & 0xf).toRadixString(16).codeUnitAt(0)); | |
} | |
} | |
return ol; | |
} | |
// To test functions urlEncode and urlDecode, replace main() with: | |
/* | |
void main() { | |
String s = "√2 is 1.414"; | |
// will be encoded as : %E2%88%9A2+is+1.414 | |
List encodedList = urlEncode(s); | |
String encodedString = new String.fromCharCodes(encodedList); | |
print("URL encoded string : $encodedString"); | |
String decodedString = urlDecode(encodedString); | |
print("URL decoded string : $decodedString"); | |
} | |
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Dart code sample : Simple shopping cart application server program. | |
Note: Do not use this code for actual applications. | |
Usage: | |
1) Run this SimpleShoppingCartServer.dart as server. | |
2) Access this server from your browser : http://localhost:8080/GooSushi | |
Ref: www.cresc.co.jp/tech/java/Google_Dart/DartLanguageGuide.pdf (in Japanese) | |
May 2012, by Cresc Corp. | |
September 2012, incorporated catch syntax and math library changes | |
October 2012, incorporated M1 changes | |
January 2013, incorporated API changes | |
February 2013, incorporated API cange (Date -> DateTime) | |
February 2013, API changes (dart:io) incorporated | |
March 2013, API changes (HttpResponse and List) incorporated | |
*/ | |
import "dart:io"; | |
import "HttpSessionManager.dart" as slib; | |
import "dart:utf" as utf; | |
import "dart:uri" as uri; | |
import 'dart:math' as Math; | |
final HOST = "127.0.0.1"; | |
final PORT = 8080; | |
final REQUEST_PATH = "/GooSushi"; | |
final LOG_REQUESTS = false; | |
final int MaxInactiveInterval = 60; // set this parameter in seconds. Set -1 to apply manager's default timeout. | |
Map<String, ShoppingCartItem> menu; // today's menu. | |
void main() { | |
menu = new Menu().menu; // prepare today's menu | |
HttpServer.bind(HOST, PORT) | |
.then((HttpServer server) { | |
server.listen( | |
(HttpRequest request) { | |
if (request.uri.path == REQUEST_PATH) { | |
requestReceivedHandler(request); | |
} | |
}); | |
print("${new DateTime.now()} Serving $REQUEST_PATH on http://${HOST}:${PORT}."); | |
}); | |
} | |
void requestReceivedHandler(HttpRequest request) { | |
HttpResponse response = request.response; | |
String htmlResponse; | |
try { | |
if (LOG_REQUESTS) print(createLogMessage(request).toString()); | |
var session = slib.getSession(request, response); | |
if (session != null){ | |
if (session.isNew()) session.setMaxInactiveInterval(MaxInactiveInterval); | |
} | |
if (LOG_REQUESTS) print(slib.createSessionLog(session, request).toString()); | |
htmlResponse = createHtmlResponse(request, session).toString(); | |
} on Exception catch (err) { | |
htmlResponse = createErrorPage(err.toString()).toString(); | |
} | |
response.headers.add("Content-Type", "text/html; charset=UTF-8"); | |
response.write(htmlResponse); | |
response.close(); | |
} | |
// Create HTML response to the request. | |
StringBuffer createHtmlResponse(HttpRequest request, slib.Session session) { | |
if (session.isNew() || request.uri.query == null) { | |
return createMenuPage(); | |
} | |
else if (request.queryParameters.containsKey("menuPage")) { | |
return createConfirmPage(request,session); | |
} | |
else if (request.queryParameters["confirmPage"].trim() == "confirmed") { | |
StringBuffer sb = createThankYouPage(session); | |
session.invalidate(); | |
return sb; | |
} | |
else if (request.queryParameters["confirmPage"].trim() == "no, re-order") { | |
return createMenuPage(cart : session.getAttribute("cart")); | |
} | |
else { | |
session.invalidate(); | |
return createErrorPage("Invalid request received."); | |
} | |
} | |
/* | |
* Shopping cart class. | |
*/ | |
class ShoppingCart { | |
double _amount; | |
List<ShoppingCartItem> _items; | |
// constructor | |
ShoppingCart() { | |
_items = new List<ShoppingCartItem>(); | |
_amount = 0.0; | |
} | |
// setter and getters | |
List get items => _items; | |
double get amount => _amount; | |
// methods | |
// get cart item with the item code | |
ShoppingCartItem getCartItem(String itemCode) { | |
for (ShoppingCartItem item in _items) { | |
if (item.itemCode == itemCode) return item; | |
} | |
return null; | |
} | |
// remove an item from the shopping cart and update the amount | |
void removeItem(int itemCode) { | |
for(int i = 0; i < _items.length; i++) { | |
if (_items[i].itemCode == itemCode) _items.removeRange(i, 1); | |
break; | |
} | |
_amount = 0.0; | |
for (ShoppingCartItem item in _items) { | |
_amount += item.perItemCost * item.qty; | |
} | |
} | |
// Add an new item and update the amount | |
void addItem(ShoppingCartItem newItem) { | |
_amount = 0.0; | |
//See if there's already an item like this in the cart | |
if (_items.isEmpty) { | |
// New, add it to the cart. | |
_items.add(newItem); | |
} else { | |
bool itemExists = false; | |
for(int i = 0; i < _items.length; i++) { | |
if (_items[i].itemCode == newItem.itemCode) { | |
_items[i] = newItem; | |
itemExists = true; | |
break; | |
} | |
} | |
if (!itemExists) _items.add(newItem); | |
} | |
for (ShoppingCartItem item in _items) { | |
_amount += item.perItemCost * item.qty; | |
} | |
} | |
} | |
/* | |
* Bean like class to set and get information about items in the shopping cart. | |
*/ | |
class ShoppingCartItem { | |
String _itemCode; | |
String _itemName; | |
int _qty; | |
double _perItemCost; | |
double _subTotal; | |
//update items in the shopping cart | |
void update(String itemCode, int qty, double perItemCost) { | |
this.itemCode = itemCode; | |
this.qty =qty; | |
this.perItemCost = perItemCost; | |
} | |
//setter and getter methods | |
String get itemCode => _itemCode; | |
void set itemCode(String itemCode) {_itemCode = itemCode;} | |
double get perItemCost => _perItemCost; | |
void set perItemCost(double perItemCost) { _perItemCost = perItemCost;} | |
int get qty => _qty; | |
void set qty(int qty) { _qty = qty;} | |
String get itemName => _itemName; | |
void set itemName(String itemName) { _itemName = itemName;} | |
double get subTotal => _subTotal; | |
void set subTotal(double subTotal) { _subTotal = subTotal; } | |
} | |
/* | |
* Menu class of the day. | |
*/ | |
class Menu { | |
Map<String, ShoppingCartItem> menu; | |
Map menuItemList; | |
Menu() { | |
menu = {}; | |
menuItemList = | |
{"100": ["Tai (Japanese red sea bream)", 360.0], | |
"110": ["Maguro (Tuna)", 360.0], | |
"120": ["Sake (Salmon)", 360.0], | |
"130": ["Hamachi (Yellowtail)", 360.0], | |
"140": ["Kanpachi (Great amberjack)", 360.0], | |
"150": ["Tobiko (Flying Fish Roe)", 520.0], | |
"160": ["Ebi (Shrimp)", 240.0], | |
"170": ["Unagi (Eel)", 520.0], | |
"180": ["Anago (Conger Eal)", 360.0], | |
"190": ["Ika (Squid)", 200.0] | |
}; | |
menuItemList.forEach((String itemCode, List item) { | |
var cartItem = new ShoppingCartItem(); | |
cartItem.itemCode = itemCode; | |
cartItem.itemName = item[0]; | |
cartItem.perItemCost = item[1]; | |
cartItem.qty = 0; | |
cartItem.subTotal = 0.0; | |
menu[itemCode] = cartItem; | |
}); | |
} | |
} | |
// Sort the shopping cart. | |
ShoppingCart sortCart(ShoppingCart cart) { | |
var newCart = new ShoppingCart(); | |
menu.forEach((String itemCode, ShoppingCartItem i) { | |
for (ShoppingCartItem item in cart.items){ | |
if (item.itemCode == itemCode) { | |
newCart.addItem(item); | |
break; | |
} | |
} | |
}); | |
return newCart; | |
} | |
// Create menu page HTML text. | |
StringBuffer createMenuPage({ShoppingCart cart: null}) { | |
var sb = new StringBuffer(""); | |
var text1 = ''' | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>SimpleShoppingCartServer</title> | |
</head> | |
<body> | |
<h1>"Goo!" Sushi</h1> | |
<h2>Today's Menu</h2><br> | |
<form method="get" action="./GooSushi"> | |
<table border="1"> | |
<tr bgcolor="#90ee90"><th align="center">Item</th><th align="center">Price<br> | |
(2 pieces)</th><th align="center"></th></tr>'''; | |
sb.write(text1); | |
menu.forEach((key, value){ | |
var text2 = ''' | |
<tr><td align="center">${makeSafe(new StringBuffer(menu[key].itemName)).toString()}</td> | |
<td align="center">${menu[key].perItemCost}</td> | |
<td align="center"><select name="pieces_${menu[key].itemCode}">'''; | |
sb.write(text2); | |
if (cart == null || cart.getCartItem(menu[key].itemCode) == null) { | |
text2 = "<option>0<option>1<option>2<option>3<option>4<option>5</select></td></tr>"; | |
} | |
else { | |
int pieces = cart.getCartItem(menu[key].itemCode).qty; | |
text2 = ""; | |
for (int i = 0; i < 6; i++) { | |
if (i == pieces) { | |
text2 = '$text2<option style="color:red" selected>$i'; | |
} | |
else { | |
text2 = "$text2<option>$i"; | |
} | |
} | |
text2 = "$text2</select></td></tr>"; | |
} | |
sb.write(text2); | |
}); | |
var text3 = ''' | |
</table><br> | |
<input type="submit" name= "menuPage" value="order"> | |
</form> | |
</body> | |
</html>'''; | |
sb.write(text3); | |
return sb; | |
} | |
// Create confirm page HTML text. | |
StringBuffer createConfirmPage(HttpRequest request, slib.Session session) { | |
// create a shopping cart | |
var cart = new ShoppingCart(); | |
request.queryParameters.forEach((String name, String value) { | |
int quantity; | |
if (name.startsWith("pieces_")) { | |
quantity = int.parse(value); | |
if (quantity != 0) { | |
var cartItem = new ShoppingCartItem(); | |
cartItem.itemCode = name.substring(7); | |
cartItem.qty = quantity; | |
cartItem.itemName = menu[cartItem.itemCode].itemName; | |
cartItem.perItemCost = menu[cartItem.itemCode].perItemCost; | |
cartItem.subTotal = cartItem.perItemCost * quantity; | |
cart.addItem(cartItem); | |
} | |
} | |
}); // cart completed | |
cart = sortCart(cart); // sort | |
session.setAttribute("cart", cart); // and bind it to the session | |
var sb = new StringBuffer(""); | |
var text1 = ''' | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>SimpleShoppingCartServer</title> | |
</head> | |
<body> | |
<h1>"Goo!" Sushi</h1> | |
<h2>Order Confirmation</h2><br> | |
<form method="get" action="./GooSushi"> | |
<table border="1"> | |
<tr bgcolor="#90ee90"><th align="center">Item</th><th align="center">Quantity</th><th align="center">Subtotal</th></tr>'''; | |
sb.write(text1); | |
var sumQty = 0; | |
cart.items.forEach((ShoppingCartItem cartItem) { | |
var text2 = ''' | |
<tr><td align="center">${makeSafe(new StringBuffer(cartItem.itemName))}</td> | |
<td align="right">${cartItem.qty}</td> | |
<td align="right">${formatNumberBy3(cartItem.subTotal)}</td></tr>'''; | |
sumQty += cartItem.qty; | |
sb.write(text2); | |
}); | |
var text3 = '''<tr><td align="center">Grand Total</td> | |
<td align="right">${sumQty}</td> | |
<td align="right" bgcolor="#fafad2">Yen ${formatNumberBy3(cart.amount)}</td></tr>'''; | |
sb.write(text3); | |
var text4 = ''' | |
</table><br> | |
<input type="submit" name= "confirmPage" value="no, re-order"> | |
<input type="submit" name= "confirmPage" value=" confirmed "> | |
</form> | |
</body> | |
</html>'''; | |
sb.write(text4); | |
return sb; | |
} | |
// Create Thank you page HTML text. | |
StringBuffer createThankYouPage(slib.Session session) { | |
var sb = new StringBuffer(""); | |
var date = new DateTime.now().toString(); | |
var text1 = ''' | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>SimpleShoppingCartServer</title> | |
</head> | |
<body> | |
<h1>"Goo!" Sushi</h1> | |
<h2>Thank you, enjoy your meal!</h2><br><br> | |
Date: ${date.substring(0, date.length-7)}<br> | |
Order number: ${session.getId()}<br> | |
Total amount: Yen ${formatNumberBy3(session.getAttribute("cart").amount)}<br><br> | |
<form method="get" action="./GooSushi"> | |
<input type="submit" name= "thankYouPage" value="come again!"> | |
</form> | |
</body> | |
</html>'''; | |
sb.write(text1); | |
return sb; | |
} | |
// Create error page HTML text. | |
StringBuffer createErrorPage(String errorMessage) { | |
return new StringBuffer(''' | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Error Page</title> | |
</head> | |
<body> | |
<h1> *** Internal Error ***</h1><br> | |
<pre>Server error occured: ${makeSafe(new StringBuffer(errorMessage)).toString()}</pre><br> | |
</body> | |
</html>'''); | |
} | |
// create log message | |
StringBuffer createLogMessage(HttpRequest request, [String bodyString]) { | |
var sb = new StringBuffer( '''request.headers.host : ${request.headers.host} | |
request.headers.port : ${request.headers.port} | |
request.connectionInfo.localPort : ${request.connectionInfo.localPort} | |
request.connectionInfo.remoteHost : ${request.connectionInfo.remoteHost} | |
request.connectionInfo.remotePort : ${request.connectionInfo.remotePort} | |
request.method : ${request.method} | |
request.persistentConnection : ${request.persistentConnection} | |
request.protocolVersion : ${request.protocolVersion} | |
request.contentLength : ${request.contentLength} | |
request.uri : ${request.uri} | |
request.uri.path : ${request.uri.path} | |
request.uri.query : ${request.uri.query} | |
request.uri.queryParameters : | |
'''); | |
request.queryParameters.forEach((key, value){ | |
sb.write(" ${key} : ${value}\n"); | |
}); | |
sb.write('''request.cookies : | |
'''); | |
request.cookies.forEach((value){ | |
sb.write(" ${value.toString()}\n"); | |
}); | |
sb.write('''request.headers.expires : ${request.headers.expires} | |
request.headers : | |
'''); | |
var str = request.headers.toString(); | |
for (int i = 0; i < str.length - 1; i++){ | |
if (str[i] == "\n") { sb.write("\n "); | |
} else { sb.write(str[i]); | |
} | |
} | |
sb.write('''\nrequest.session.id : ${request.session.id} | |
requset.session.isNew : ${request.session.isNew}'''); | |
if (request.method == "POST") { | |
var enctype = request.headers["content-type"]; | |
if (enctype[0].contains("text")) { | |
sb.write("request body string : ${bodyString.replaceAll('+', ' ')}"); | |
} else if (enctype[0].contains("urlencoded")) { | |
sb.write("request body string (URL decoded): ${uri.decodeUri(bodyString)}"); | |
} | |
} | |
sb.write("\n"); | |
return sb; | |
} | |
// make safe string buffer data as HTML text | |
StringBuffer makeSafe(StringBuffer b) { | |
var s = b.toString(); | |
b = new StringBuffer(); | |
for (int i = 0; i < s.length; i++){ | |
if (s[i] == '&') { b.write('&'); | |
} else if (s[i] == '"') { b.write('"'); | |
} else if (s[i] == "'") { b.write('''); | |
} else if (s[i] == '<') { b.write('<'); | |
} else if (s[i] == '>') { b.write('>'); | |
} else { b.write(s[i]); | |
} | |
} | |
return b; | |
} | |
// function to format a number with separators. returns formatted number. | |
// original JS Author: Robert Hashemian (http://www.hashemian.com/) | |
// modified for Dart, 2012, by Cresc | |
// num - the number to be formatted | |
// decpoint - the decimal point character. if skipped, "." is used | |
// sep - the separator character. if skipped, "," is used | |
String formatNumberBy3(num number, {String decpoint: '.', String sep: ','}) { | |
// need a string for operations | |
String numstr = number.toString(); | |
// separate the whole number and the fraction if possible | |
var a = numstr.split(decpoint); | |
var x = a[0]; // decimal | |
var y; | |
bool nfr = false; // no fraction flag | |
if (a.length == 1) { nfr = true; | |
} else { y = a[1]; | |
} // fraction | |
var z = ""; | |
var p = x.length; | |
if (p > 3) { | |
for (int i = p-1; i >= 0; i--) { | |
z = '$z${x[i]}'; | |
if ((i > 0) && ((p-i) % 3 == 0) && (x[i-1].codeUnitAt(0) >= '0'.codeUnitAt(0)) | |
&& (x[i-1].codeUnitAt(0) <= '9'.codeUnitAt(0))) { z = '$z,'; | |
} | |
} | |
// reverse z to get back the number | |
x = ''; | |
for (int i = z.length - 1; i>=0; i--) x = '$x${z[i]}'; | |
} | |
// add the fraction back in, if it was there | |
if (nfr) return x; else return '$x$decpoint$y'; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment