Skip to content

Instantly share code, notes, and snippets.

@mitsuoka
Last active October 6, 2015 05:07
Show Gist options
  • Save mitsuoka/2941231 to your computer and use it in GitHub Desktop.
Save mitsuoka/2941231 to your computer and use it in GitHub Desktop.
Cookie based HTTP session manager library and its sample applications
/*
*** 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;
}
/*
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('&amp;');
} else if (s[i] == '"') { b.write('&quot;');
} else if (s[i] == "'") { b.write('&#39;');
} else if (s[i] == '<') { b.write('&lt;');
} else if (s[i] == '>') { b.write('&gt;');
} 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");
}
*/
/*
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('&amp;');
} else if (s[i] == '"') { b.write('&quot;');
} else if (s[i] == "'") { b.write('&#39;');
} else if (s[i] == '<') { b.write('&lt;');
} else if (s[i] == '>') { b.write('&gt;');
} 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