").append(jQuery.parseHTML(responseText)).find(selector) :
+
+ // Otherwise use the full result
+ responseText);
+
+ }).complete(callback && function (jqXHR, status) {
+ self.each(callback, response || [jqXHR.responseText, status, jqXHR]);
+ });
+ }
+
+ return this;
+ };
+
+
+
+
+ jQuery.expr.filters.animated = function (elem) {
+ return jQuery.grep(jQuery.timers, function (fn) {
+ return elem === fn.elem;
+ }).length;
+ };
+
+
+
+
+
+ var docElem = window.document.documentElement;
+
+ /**
+ * Gets a window from an element
+ */
+ function getWindow(elem) {
+ return jQuery.isWindow(elem) ?
+ elem :
+ elem.nodeType === 9 ?
+ elem.defaultView || elem.parentWindow :
+ false;
+ }
+
+ jQuery.offset = {
+ setOffset: function (elem, options, i) {
+ var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
+ position = jQuery.css(elem, "position"),
+ curElem = jQuery(elem),
+ props = {};
+
+ // set position first, in-case top/left are set even on static elem
+ if (position === "static") {
+ elem.style.position = "relative";
+ }
+
+ curOffset = curElem.offset();
+ curCSSTop = jQuery.css(elem, "top");
+ curCSSLeft = jQuery.css(elem, "left");
+ calculatePosition = (position === "absolute" || position === "fixed") &&
+ jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1;
+
+ // need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+ if (calculatePosition) {
+ curPosition = curElem.position();
+ curTop = curPosition.top;
+ curLeft = curPosition.left;
+ } else {
+ curTop = parseFloat(curCSSTop) || 0;
+ curLeft = parseFloat(curCSSLeft) || 0;
+ }
+
+ if (jQuery.isFunction(options)) {
+ options = options.call(elem, i, curOffset);
+ }
+
+ if (options.top != null) {
+ props.top = (options.top - curOffset.top) + curTop;
+ }
+ if (options.left != null) {
+ props.left = (options.left - curOffset.left) + curLeft;
+ }
+
+ if ("using" in options) {
+ options.using.call(elem, props);
+ } else {
+ curElem.css(props);
+ }
+ }
+ };
+
+ jQuery.fn.extend({
+ offset: function (options) {
+ if (arguments.length) {
+ return options === undefined ?
+ this :
+ this.each(function (i) {
+ jQuery.offset.setOffset(this, options, i);
+ });
+ }
+
+ var docElem, win,
+ box = { top: 0, left: 0 },
+ elem = this[0],
+ doc = elem && elem.ownerDocument;
+
+ if (!doc) {
+ return;
+ }
+
+ docElem = doc.documentElement;
+
+ // Make sure it's not a disconnected DOM node
+ if (!jQuery.contains(docElem, elem)) {
+ return box;
+ }
+
+ // If we don't have gBCR, just use 0,0 rather than error
+ // BlackBerry 5, iOS 3 (original iPhone)
+ if (typeof elem.getBoundingClientRect !== strundefined) {
+ box = elem.getBoundingClientRect();
+ }
+ win = getWindow(doc);
+ return {
+ top: box.top + (win.pageYOffset || docElem.scrollTop) - (docElem.clientTop || 0),
+ left: box.left + (win.pageXOffset || docElem.scrollLeft) - (docElem.clientLeft || 0)
+ };
+ },
+
+ position: function () {
+ if (!this[0]) {
+ return;
+ }
+
+ var offsetParent, offset,
+ parentOffset = { top: 0, left: 0 },
+ elem = this[0];
+
+ // fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is its only offset parent
+ if (jQuery.css(elem, "position") === "fixed") {
+ // we assume that getBoundingClientRect is available when computed position is fixed
+ offset = elem.getBoundingClientRect();
+ } else {
+ // Get *real* offsetParent
+ offsetParent = this.offsetParent();
+
+ // Get correct offsets
+ offset = this.offset();
+ if (!jQuery.nodeName(offsetParent[0], "html")) {
+ parentOffset = offsetParent.offset();
+ }
+
+ // Add offsetParent borders
+ parentOffset.top += jQuery.css(offsetParent[0], "borderTopWidth", true);
+ parentOffset.left += jQuery.css(offsetParent[0], "borderLeftWidth", true);
+ }
+
+ // Subtract parent offsets and element margins
+ // note: when an element has margin: auto the offsetLeft and marginLeft
+ // are the same in Safari causing offset.left to incorrectly be 0
+ return {
+ top: offset.top - parentOffset.top - jQuery.css(elem, "marginTop", true),
+ left: offset.left - parentOffset.left - jQuery.css(elem, "marginLeft", true)
+ };
+ },
+
+ offsetParent: function () {
+ return this.map(function () {
+ var offsetParent = this.offsetParent || docElem;
+
+ while (offsetParent && (!jQuery.nodeName(offsetParent, "html") && jQuery.css(offsetParent, "position") === "static")) {
+ offsetParent = offsetParent.offsetParent;
+ }
+ return offsetParent || docElem;
+ });
+ }
+ });
+
+ // Create scrollLeft and scrollTop methods
+ jQuery.each({ scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function (method, prop) {
+ var top = /Y/.test(prop);
+
+ jQuery.fn[method] = function (val) {
+ return access(this, function (elem, method, val) {
+ var win = getWindow(elem);
+
+ if (val === undefined) {
+ return win ? (prop in win) ? win[prop] :
+ win.document.documentElement[method] :
+ elem[method];
+ }
+
+ if (win) {
+ win.scrollTo(
+ !top ? val : jQuery(win).scrollLeft(),
+ top ? val : jQuery(win).scrollTop()
+ );
+
+ } else {
+ elem[method] = val;
+ }
+ }, method, val, arguments.length, null);
+ };
+ });
+
+ // Add the top/left cssHooks using jQuery.fn.position
+ // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
+ // getComputedStyle returns percent when specified for top/left/bottom/right
+ // rather than make the css module depend on the offset module, we just check for it here
+ jQuery.each(["top", "left"], function (i, prop) {
+ jQuery.cssHooks[prop] = addGetHookIf(support.pixelPosition,
+ function (elem, computed) {
+ if (computed) {
+ computed = curCSS(elem, prop);
+ // if curCSS returns percentage, fallback to offset
+ return rnumnonpx.test(computed) ?
+ jQuery(elem).position()[prop] + "px" :
+ computed;
+ }
+ }
+ );
+ });
+
+
+ // Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
+ jQuery.each({ Height: "height", Width: "width" }, function (name, type) {
+ jQuery.each({ padding: "inner" + name, content: type, "": "outer" + name }, function (defaultExtra, funcName) {
+ // margin is only for outerHeight, outerWidth
+ jQuery.fn[funcName] = function (margin, value) {
+ var chainable = arguments.length && (defaultExtra || typeof margin !== "boolean"),
+ extra = defaultExtra || (margin === true || value === true ? "margin" : "border");
+
+ return access(this, function (elem, type, value) {
+ var doc;
+
+ if (jQuery.isWindow(elem)) {
+ // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
+ // isn't a whole lot we can do. See pull request at this URL for discussion:
+ // https://github.com/jquery/jquery/pull/764
+ return elem.document.documentElement["client" + name];
+ }
+
+ // Get document width or height
+ if (elem.nodeType === 9) {
+ doc = elem.documentElement;
+
+ // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest
+ // unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.
+ return Math.max(
+ elem.body["scroll" + name], doc["scroll" + name],
+ elem.body["offset" + name], doc["offset" + name],
+ doc["client" + name]
+ );
+ }
+
+ return value === undefined ?
+ // Get width or height on the element, requesting but not forcing parseFloat
+ jQuery.css(elem, type, extra) :
+
+ // Set width or height on the element
+ jQuery.style(elem, type, value, extra);
+ }, type, chainable ? margin : undefined, chainable, null);
+ };
+ });
+ });
+
+
+ // The number of elements contained in the matched element set
+ jQuery.fn.size = function () {
+ return this.length;
+ };
+
+ jQuery.fn.andSelf = jQuery.fn.addBack;
+
+
+
+
+ // Register as a named AMD module, since jQuery can be concatenated with other
+ // files that may use define, but not via a proper concatenation script that
+ // understands anonymous AMD modules. A named AMD is safest and most robust
+ // way to register. Lowercase jquery is used because AMD module names are
+ // derived from file names, and jQuery is normally delivered in a lowercase
+ // file name. Do this after creating the global so that if an AMD module wants
+ // to call noConflict to hide this version of jQuery, it will work.
+
+ // Note that for maximum portability, libraries that are not jQuery should
+ // declare themselves as anonymous modules, and avoid setting a global if an
+ // AMD loader is present. jQuery is a special case. For more information, see
+ // https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
+
+ if (typeof define === "function" && define.amd) {
+ define("jquery", [], function () {
+ return jQuery;
+ });
+ }
+
+
+
+
+ var
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$;
+
+ jQuery.noConflict = function (deep) {
+ if (window.$ === jQuery) {
+ window.$ = _$;
+ }
+
+ if (deep && window.jQuery === jQuery) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+ };
+
+ // Expose jQuery and $ identifiers, even in
+ // AMD (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
+ // and CommonJS for browser emulators (#13566)
+ if (typeof noGlobal === strundefined) {
+ window.jQuery = window.$ = jQuery;
+ }
+
+
+
+
+ return jQuery;
+
+}));
\ No newline at end of file
diff --git a/_examples/websocket/third-party-socketio/public/socket.io-1.3.7.js b/_examples/websocket/third-party-socketio/public/socket.io-1.3.7.js
new file mode 100644
index 00000000..e43c4ad0
--- /dev/null
+++ b/_examples/websocket/third-party-socketio/public/socket.io-1.3.7.js
@@ -0,0 +1,3 @@
+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.io=e()}}(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o
0&&!this.encoding){var pack=this.packetBuffer.shift();this.packet(pack)}};Manager.prototype.cleanup=function(){var sub;while(sub=this.subs.shift())sub.destroy();this.packetBuffer=[];this.encoding=false;this.decoder.destroy()};Manager.prototype.close=Manager.prototype.disconnect=function(){this.skipReconnect=true;this.backoff.reset();this.readyState="closed";this.engine&&this.engine.close()};Manager.prototype.onclose=function(reason){debug("close");this.cleanup();this.backoff.reset();this.readyState="closed";this.emit("close",reason);if(this._reconnection&&!this.skipReconnect){this.reconnect()}};Manager.prototype.reconnect=function(){if(this.reconnecting||this.skipReconnect)return this;var self=this;if(this.backoff.attempts>=this._reconnectionAttempts){debug("reconnect failed");this.backoff.reset();this.emitAll("reconnect_failed");this.reconnecting=false}else{var delay=this.backoff.duration();debug("will wait %dms before reconnect attempt",delay);this.reconnecting=true;var timer=setTimeout(function(){if(self.skipReconnect)return;debug("attempting reconnect");self.emitAll("reconnect_attempt",self.backoff.attempts);self.emitAll("reconnecting",self.backoff.attempts);if(self.skipReconnect)return;self.open(function(err){if(err){debug("reconnect attempt error");self.reconnecting=false;self.reconnect();self.emitAll("reconnect_error",err.data)}else{debug("reconnect success");self.onreconnect()}})},delay);this.subs.push({destroy:function(){clearTimeout(timer)}})}};Manager.prototype.onreconnect=function(){var attempt=this.backoff.attempts;this.reconnecting=false;this.backoff.reset();this.updateSocketIds();this.emitAll("reconnect",attempt)}},{"./on":4,"./socket":5,"./url":6,backo2:7,"component-bind":8,"component-emitter":9,debug:10,"engine.io-client":11,indexof:40,"object-component":41,"socket.io-parser":44}],4:[function(_dereq_,module,exports){module.exports=on;function on(obj,ev,fn){obj.on(ev,fn);return{destroy:function(){obj.removeListener(ev,fn)}}}},{}],5:[function(_dereq_,module,exports){var parser=_dereq_("socket.io-parser");var Emitter=_dereq_("component-emitter");var toArray=_dereq_("to-array");var on=_dereq_("./on");var bind=_dereq_("component-bind");var debug=_dereq_("debug")("socket.io-client:socket");var hasBin=_dereq_("has-binary");module.exports=exports=Socket;var events={connect:1,connect_error:1,connect_timeout:1,disconnect:1,error:1,reconnect:1,reconnect_attempt:1,reconnect_failed:1,reconnect_error:1,reconnecting:1};var emit=Emitter.prototype.emit;function Socket(io,nsp){this.io=io;this.nsp=nsp;this.json=this;this.ids=0;this.acks={};if(this.io.autoConnect)this.open();this.receiveBuffer=[];this.sendBuffer=[];this.connected=false;this.disconnected=true}Emitter(Socket.prototype);Socket.prototype.subEvents=function(){if(this.subs)return;var io=this.io;this.subs=[on(io,"open",bind(this,"onopen")),on(io,"packet",bind(this,"onpacket")),on(io,"close",bind(this,"onclose"))]};Socket.prototype.open=Socket.prototype.connect=function(){if(this.connected)return this;this.subEvents();this.io.open();if("open"==this.io.readyState)this.onopen();return this};Socket.prototype.send=function(){var args=toArray(arguments);args.unshift("message");this.emit.apply(this,args);return this};Socket.prototype.emit=function(ev){if(events.hasOwnProperty(ev)){emit.apply(this,arguments);return this}var args=toArray(arguments);var parserType=parser.EVENT;if(hasBin(args)){parserType=parser.BINARY_EVENT}var packet={type:parserType,data:args};if("function"==typeof args[args.length-1]){debug("emitting packet with ack id %d",this.ids);this.acks[this.ids]=args.pop();packet.id=this.ids++}if(this.connected){this.packet(packet)}else{this.sendBuffer.push(packet)}return this};Socket.prototype.packet=function(packet){packet.nsp=this.nsp;this.io.packet(packet)};Socket.prototype.onopen=function(){debug("transport is open - connecting");if("/"!=this.nsp){this.packet({type:parser.CONNECT})}};Socket.prototype.onclose=function(reason){debug("close (%s)",reason);this.connected=false;this.disconnected=true;delete this.id;this.emit("disconnect",reason)};Socket.prototype.onpacket=function(packet){if(packet.nsp!=this.nsp)return;switch(packet.type){case parser.CONNECT:this.onconnect();break;case parser.EVENT:this.onevent(packet);break;case parser.BINARY_EVENT:this.onevent(packet);break;case parser.ACK:this.onack(packet);break;case parser.BINARY_ACK:this.onack(packet);break;case parser.DISCONNECT:this.ondisconnect();break;case parser.ERROR:this.emit("error",packet.data);break}};Socket.prototype.onevent=function(packet){var args=packet.data||[];debug("emitting event %j",args);if(null!=packet.id){debug("attaching ack callback to event");args.push(this.ack(packet.id))}if(this.connected){emit.apply(this,args)}else{this.receiveBuffer.push(args)}};Socket.prototype.ack=function(id){var self=this;var sent=false;return function(){if(sent)return;sent=true;var args=toArray(arguments);debug("sending ack %j",args);var type=hasBin(args)?parser.BINARY_ACK:parser.ACK;self.packet({type:type,id:id,data:args})}};Socket.prototype.onack=function(packet){debug("calling ack %s with %j",packet.id,packet.data);var fn=this.acks[packet.id];fn.apply(this,packet.data);delete this.acks[packet.id]};Socket.prototype.onconnect=function(){this.connected=true;this.disconnected=false;this.emit("connect");this.emitBuffered()};Socket.prototype.emitBuffered=function(){var i;for(i=0;i0&&opts.jitter<=1?opts.jitter:0;this.attempts=0}Backoff.prototype.duration=function(){var ms=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var rand=Math.random();var deviation=Math.floor(rand*this.jitter*ms);ms=(Math.floor(rand*10)&1)==0?ms-deviation:ms+deviation}return Math.min(ms,this.max)|0};Backoff.prototype.reset=function(){this.attempts=0};Backoff.prototype.setMin=function(min){this.ms=min};Backoff.prototype.setMax=function(max){this.max=max};Backoff.prototype.setJitter=function(jitter){this.jitter=jitter}},{}],8:[function(_dereq_,module,exports){var slice=[].slice;module.exports=function(obj,fn){if("string"==typeof fn)fn=obj[fn];if("function"!=typeof fn)throw new Error("bind() requires a function");var args=slice.call(arguments,2);return function(){return fn.apply(obj,args.concat(slice.call(arguments)))}}},{}],9:[function(_dereq_,module,exports){module.exports=Emitter;function Emitter(obj){if(obj)return mixin(obj)}function mixin(obj){for(var key in Emitter.prototype){obj[key]=Emitter.prototype[key]}return obj}Emitter.prototype.on=Emitter.prototype.addEventListener=function(event,fn){this._callbacks=this._callbacks||{};(this._callbacks[event]=this._callbacks[event]||[]).push(fn);return this};Emitter.prototype.once=function(event,fn){var self=this;this._callbacks=this._callbacks||{};function on(){self.off(event,on);fn.apply(this,arguments)}on.fn=fn;this.on(event,on);return this};Emitter.prototype.off=Emitter.prototype.removeListener=Emitter.prototype.removeAllListeners=Emitter.prototype.removeEventListener=function(event,fn){this._callbacks=this._callbacks||{};if(0==arguments.length){this._callbacks={};return this}var callbacks=this._callbacks[event];if(!callbacks)return this;if(1==arguments.length){delete this._callbacks[event];return this}var cb;for(var i=0;i=hour)return(ms/hour).toFixed(1)+"h";if(ms>=min)return(ms/min).toFixed(1)+"m";if(ms>=sec)return(ms/sec|0)+"s";return ms+"ms"};debug.enabled=function(name){for(var i=0,len=debug.skips.length;i';iframe=document.createElement(html)}catch(e){iframe=document.createElement("iframe");iframe.name=self.iframeId;iframe.src="javascript:0"}iframe.id=self.iframeId;self.form.appendChild(iframe);self.iframe=iframe}initIframe();data=data.replace(rEscapedNewline,"\\\n");this.area.value=data.replace(rNewline,"\\n");try{this.form.submit()}catch(e){}if(this.iframe.attachEvent){this.iframe.onreadystatechange=function(){if(self.iframe.readyState=="complete"){complete()}}}else{this.iframe.onload=complete}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{"./polling":18,"component-inherit":21}],17:[function(_dereq_,module,exports){(function(global){var XMLHttpRequest=_dereq_("xmlhttprequest");var Polling=_dereq_("./polling");var Emitter=_dereq_("component-emitter");var inherit=_dereq_("component-inherit");var debug=_dereq_("debug")("engine.io-client:polling-xhr");module.exports=XHR;module.exports.Request=Request;function empty(){}function XHR(opts){Polling.call(this,opts);if(global.location){var isSSL="https:"==location.protocol;var port=location.port;if(!port){port=isSSL?443:80}this.xd=opts.hostname!=global.location.hostname||port!=opts.port;this.xs=opts.secure!=isSSL}}inherit(XHR,Polling);XHR.prototype.supportsBinary=true;XHR.prototype.request=function(opts){opts=opts||{};opts.uri=this.uri();opts.xd=this.xd;opts.xs=this.xs;opts.agent=this.agent||false;opts.supportsBinary=this.supportsBinary;opts.enablesXDR=this.enablesXDR;opts.pfx=this.pfx;opts.key=this.key;opts.passphrase=this.passphrase;opts.cert=this.cert;opts.ca=this.ca;opts.ciphers=this.ciphers;opts.rejectUnauthorized=this.rejectUnauthorized;return new Request(opts)};XHR.prototype.doWrite=function(data,fn){var isBinary=typeof data!=="string"&&data!==undefined;var req=this.request({method:"POST",data:data,isBinary:isBinary});var self=this;req.on("success",fn);req.on("error",function(err){self.onError("xhr post error",err)});this.sendXhr=req};XHR.prototype.doPoll=function(){debug("xhr poll");var req=this.request();var self=this;req.on("data",function(data){self.onData(data)});req.on("error",function(err){self.onError("xhr poll error",err)});this.pollXhr=req};function Request(opts){this.method=opts.method||"GET";this.uri=opts.uri;this.xd=!!opts.xd;this.xs=!!opts.xs;this.async=false!==opts.async;this.data=undefined!=opts.data?opts.data:null;this.agent=opts.agent;this.isBinary=opts.isBinary;this.supportsBinary=opts.supportsBinary;this.enablesXDR=opts.enablesXDR;this.pfx=opts.pfx;this.key=opts.key;this.passphrase=opts.passphrase;this.cert=opts.cert;this.ca=opts.ca;this.ciphers=opts.ciphers;this.rejectUnauthorized=opts.rejectUnauthorized;this.create()}Emitter(Request.prototype);Request.prototype.create=function(){var opts={agent:this.agent,xdomain:this.xd,xscheme:this.xs,enablesXDR:this.enablesXDR};opts.pfx=this.pfx;opts.key=this.key;opts.passphrase=this.passphrase;opts.cert=this.cert;opts.ca=this.ca;opts.ciphers=this.ciphers;opts.rejectUnauthorized=this.rejectUnauthorized;var xhr=this.xhr=new XMLHttpRequest(opts);var self=this;try{debug("xhr open %s: %s",this.method,this.uri);xhr.open(this.method,this.uri,this.async);if(this.supportsBinary){xhr.responseType="arraybuffer"}if("POST"==this.method){try{if(this.isBinary){xhr.setRequestHeader("Content-type","application/octet-stream")}else{xhr.setRequestHeader("Content-type","text/plain;charset=UTF-8")}}catch(e){}}if("withCredentials"in xhr){xhr.withCredentials=true}if(this.hasXDR()){xhr.onload=function(){self.onLoad()};xhr.onerror=function(){self.onError(xhr.responseText)}}else{xhr.onreadystatechange=function(){if(4!=xhr.readyState)return;if(200==xhr.status||1223==xhr.status){self.onLoad()}else{setTimeout(function(){self.onError(xhr.status)},0)}}}debug("xhr data %s",this.data);xhr.send(this.data)}catch(e){setTimeout(function(){self.onError(e)},0);return}if(global.document){this.index=Request.requestsCount++;Request.requests[this.index]=this}};Request.prototype.onSuccess=function(){this.emit("success");this.cleanup()};Request.prototype.onData=function(data){this.emit("data",data);this.onSuccess()};Request.prototype.onError=function(err){this.emit("error",err);this.cleanup(true)};Request.prototype.cleanup=function(fromError){if("undefined"==typeof this.xhr||null===this.xhr){return}if(this.hasXDR()){this.xhr.onload=this.xhr.onerror=empty}else{this.xhr.onreadystatechange=empty}if(fromError){try{this.xhr.abort()}catch(e){}}if(global.document){delete Request.requests[this.index]}this.xhr=null};Request.prototype.onLoad=function(){var data;try{var contentType;try{contentType=this.xhr.getResponseHeader("Content-Type").split(";")[0]}catch(e){}if(contentType==="application/octet-stream"){data=this.xhr.response}else{if(!this.supportsBinary){data=this.xhr.responseText}else{data="ok"}}}catch(e){this.onError(e)}if(null!=data){this.onData(data)}};Request.prototype.hasXDR=function(){return"undefined"!==typeof global.XDomainRequest&&!this.xs&&this.enablesXDR};Request.prototype.abort=function(){this.cleanup()};if(global.document){Request.requestsCount=0;Request.requests={};if(global.attachEvent){global.attachEvent("onunload",unloadHandler)}else if(global.addEventListener){global.addEventListener("beforeunload",unloadHandler,false)}}function unloadHandler(){for(var i in Request.requests){if(Request.requests.hasOwnProperty(i)){Request.requests[i].abort()}}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{"./polling":18,"component-emitter":9,"component-inherit":21,debug:22,xmlhttprequest:20}],18:[function(_dereq_,module,exports){var Transport=_dereq_("../transport");var parseqs=_dereq_("parseqs");var parser=_dereq_("engine.io-parser");var inherit=_dereq_("component-inherit");var debug=_dereq_("debug")("engine.io-client:polling");module.exports=Polling;var hasXHR2=function(){var XMLHttpRequest=_dereq_("xmlhttprequest");var xhr=new XMLHttpRequest({xdomain:false});return null!=xhr.responseType}();function Polling(opts){var forceBase64=opts&&opts.forceBase64;if(!hasXHR2||forceBase64){this.supportsBinary=false}Transport.call(this,opts)}inherit(Polling,Transport);Polling.prototype.name="polling";Polling.prototype.doOpen=function(){this.poll()};Polling.prototype.pause=function(onPause){var pending=0;var self=this;this.readyState="pausing";function pause(){debug("paused");self.readyState="paused";onPause()}if(this.polling||!this.writable){var total=0;if(this.polling){debug("we are currently polling - waiting to pause");total++;this.once("pollComplete",function(){debug("pre-pause polling complete");--total||pause()})}if(!this.writable){debug("we are currently writing - waiting to pause");total++;this.once("drain",function(){debug("pre-pause writing complete");--total||pause()})}}else{pause()}};Polling.prototype.poll=function(){debug("polling");this.polling=true;this.doPoll();this.emit("poll")};Polling.prototype.onData=function(data){var self=this;debug("polling got data %s",data);var callback=function(packet,index,total){if("opening"==self.readyState){self.onOpen()}if("close"==packet.type){self.onClose();return false}self.onPacket(packet)};parser.decodePayload(data,this.socket.binaryType,callback);if("closed"!=this.readyState){this.polling=false;this.emit("pollComplete");if("open"==this.readyState){this.poll()}else{debug('ignoring poll - transport state "%s"',this.readyState)}}};Polling.prototype.doClose=function(){var self=this;function close(){debug("writing close packet");self.write([{type:"close"}])}if("open"==this.readyState){debug("transport open - closing");close()}else{debug("transport not open - deferring close");this.once("open",close)}};Polling.prototype.write=function(packets){var self=this;this.writable=false;var callbackfn=function(){self.writable=true;self.emit("drain")};var self=this;parser.encodePayload(packets,this.supportsBinary,function(data){self.doWrite(data,callbackfn)})};Polling.prototype.uri=function(){var query=this.query||{};var schema=this.secure?"https":"http";var port="";if(false!==this.timestampRequests){query[this.timestampParam]=+new Date+"-"+Transport.timestamps++}if(!this.supportsBinary&&!query.sid){query.b64=1}query=parseqs.encode(query);if(this.port&&("https"==schema&&this.port!=443||"http"==schema&&this.port!=80)){port=":"+this.port}if(query.length){query="?"+query}return schema+"://"+this.hostname+port+this.path+query}},{"../transport":14,"component-inherit":21,debug:22,"engine.io-parser":25,parseqs:33,xmlhttprequest:20}],19:[function(_dereq_,module,exports){var Transport=_dereq_("../transport");var parser=_dereq_("engine.io-parser");var parseqs=_dereq_("parseqs");var inherit=_dereq_("component-inherit");var debug=_dereq_("debug")("engine.io-client:websocket");var WebSocket=_dereq_("ws");module.exports=WS;function WS(opts){var forceBase64=opts&&opts.forceBase64;if(forceBase64){this.supportsBinary=false}Transport.call(this,opts)}inherit(WS,Transport);WS.prototype.name="websocket";WS.prototype.supportsBinary=true;WS.prototype.doOpen=function(){if(!this.check()){return}var self=this;var uri=this.uri();var protocols=void 0;var opts={agent:this.agent};opts.pfx=this.pfx;opts.key=this.key;opts.passphrase=this.passphrase;opts.cert=this.cert;opts.ca=this.ca;opts.ciphers=this.ciphers;opts.rejectUnauthorized=this.rejectUnauthorized;this.ws=new WebSocket(uri,protocols,opts);if(this.ws.binaryType===undefined){this.supportsBinary=false}this.ws.binaryType="arraybuffer";this.addEventListeners()};WS.prototype.addEventListeners=function(){var self=this;this.ws.onopen=function(){self.onOpen()};this.ws.onclose=function(){self.onClose()};this.ws.onmessage=function(ev){self.onData(ev.data)};this.ws.onerror=function(e){self.onError("websocket error",e)}};if("undefined"!=typeof navigator&&/iPad|iPhone|iPod/i.test(navigator.userAgent)){WS.prototype.onData=function(data){var self=this;setTimeout(function(){Transport.prototype.onData.call(self,data)},0)}}WS.prototype.write=function(packets){var self=this;this.writable=false;for(var i=0,l=packets.length;i=31}exports.formatters.j=function(v){return JSON.stringify(v)};function formatArgs(){var args=arguments;var useColors=this.useColors;args[0]=(useColors?"%c":"")+this.namespace+(useColors?" %c":" ")+args[0]+(useColors?"%c ":" ")+"+"+exports.humanize(this.diff);if(!useColors)return args;var c="color: "+this.color;args=[args[0],c,"color: inherit"].concat(Array.prototype.slice.call(args,1));var index=0;var lastC=0;args[0].replace(/%[a-z%]/g,function(match){if("%%"===match)return;index++;if("%c"===match){lastC=index}});args.splice(lastC,0,c);return args}function log(){return"object"==typeof console&&"function"==typeof console.log&&Function.prototype.apply.call(console.log,console,arguments)}function save(namespaces){try{if(null==namespaces){localStorage.removeItem("debug")}else{localStorage.debug=namespaces}}catch(e){}}function load(){var r;try{r=localStorage.debug}catch(e){}return r}exports.enable(load())},{"./debug":23}],23:[function(_dereq_,module,exports){exports=module.exports=debug;exports.coerce=coerce;exports.disable=disable;exports.enable=enable;exports.enabled=enabled;exports.humanize=_dereq_("ms");exports.names=[];exports.skips=[];exports.formatters={};var prevColor=0;var prevTime;function selectColor(){return exports.colors[prevColor++%exports.colors.length]}function debug(namespace){function disabled(){}disabled.enabled=false;function enabled(){var self=enabled;var curr=+new Date;var ms=curr-(prevTime||curr);self.diff=ms;self.prev=prevTime;self.curr=curr;prevTime=curr;if(null==self.useColors)self.useColors=exports.useColors();if(null==self.color&&self.useColors)self.color=selectColor();var args=Array.prototype.slice.call(arguments);args[0]=exports.coerce(args[0]);if("string"!==typeof args[0]){args=["%o"].concat(args)}var index=0;args[0]=args[0].replace(/%([a-z%])/g,function(match,format){if(match==="%%")return match;index++;var formatter=exports.formatters[format];if("function"===typeof formatter){var val=args[index];match=formatter.call(self,val);args.splice(index,1);index--}return match});if("function"===typeof exports.formatArgs){args=exports.formatArgs.apply(self,args)}var logFn=enabled.log||exports.log||console.log.bind(console);logFn.apply(self,args)}enabled.enabled=true;var fn=exports.enabled(namespace)?enabled:disabled;fn.namespace=namespace;return fn}function enable(namespaces){exports.save(namespaces);var split=(namespaces||"").split(/[\s,]+/);var len=split.length;for(var i=0;i=d)return Math.round(ms/d)+"d";if(ms>=h)return Math.round(ms/h)+"h";if(ms>=m)return Math.round(ms/m)+"m";if(ms>=s)return Math.round(ms/s)+"s";return ms+"ms"}function long(ms){return plural(ms,d,"day")||plural(ms,h,"hour")||plural(ms,m,"minute")||plural(ms,s,"second")||ms+" ms"}function plural(ms,n,name){if(ms1){return{type:packetslist[type],data:data.substring(1)}}else{return{type:packetslist[type]}}}var asArray=new Uint8Array(data);var type=asArray[0];var rest=sliceBuffer(data,1);if(Blob&&binaryType==="blob"){rest=new Blob([rest])}return{type:packetslist[type],data:rest}};exports.decodeBase64Packet=function(msg,binaryType){var type=packetslist[msg.charAt(0)];if(!global.ArrayBuffer){return{type:type,data:{base64:true,data:msg.substr(1)}}}var data=base64encoder.decode(msg.substr(1));if(binaryType==="blob"&&Blob){data=new Blob([data])}return{type:type,data:data}};exports.encodePayload=function(packets,supportsBinary,callback){if(typeof supportsBinary=="function"){callback=supportsBinary;supportsBinary=null}var isBinary=hasBinary(packets);if(supportsBinary&&isBinary){if(Blob&&!dontSendBlobs){return exports.encodePayloadAsBlob(packets,callback)}return exports.encodePayloadAsArrayBuffer(packets,callback)}if(!packets.length){return callback("0:")}function setLengthHeader(message){return message.length+":"+message}function encodeOne(packet,doneCallback){exports.encodePacket(packet,!isBinary?false:supportsBinary,true,function(message){doneCallback(null,setLengthHeader(message))})}map(packets,encodeOne,function(err,results){return callback(results.join(""))})};function map(ary,each,done){var result=new Array(ary.length);var next=after(ary.length,done);var eachWithIndex=function(i,el,cb){each(el,function(error,msg){result[i]=msg;cb(error,result)})};for(var i=0;i0){var tailArray=new Uint8Array(bufferTail);var isString=tailArray[0]===0;var msgLength="";for(var i=1;;i++){if(tailArray[i]==255)break;if(msgLength.length>310){numberTooLong=true;break}msgLength+=tailArray[i]}if(numberTooLong)return callback(err,0,1);bufferTail=sliceBuffer(bufferTail,2+msgLength.length);msgLength=parseInt(msgLength);var msg=sliceBuffer(bufferTail,0,msgLength);if(isString){try{msg=String.fromCharCode.apply(null,new Uint8Array(msg))}catch(e){var typed=new Uint8Array(msg);msg="";for(var i=0;ibytes){end=bytes}if(start>=bytes||start>=end||bytes===0){return new ArrayBuffer(0)}var abv=new Uint8Array(arraybuffer);var result=new Uint8Array(end-start);for(var i=start,ii=0;i>2];base64+=chars[(bytes[i]&3)<<4|bytes[i+1]>>4];base64+=chars[(bytes[i+1]&15)<<2|bytes[i+2]>>6];base64+=chars[bytes[i+2]&63]}if(len%3===2){base64=base64.substring(0,base64.length-1)+"="}else if(len%3===1){base64=base64.substring(0,base64.length-2)+"=="}return base64};exports.decode=function(base64){var bufferLength=base64.length*.75,len=base64.length,i,p=0,encoded1,encoded2,encoded3,encoded4;if(base64[base64.length-1]==="="){bufferLength--;if(base64[base64.length-2]==="="){bufferLength--}}var arraybuffer=new ArrayBuffer(bufferLength),bytes=new Uint8Array(arraybuffer);for(i=0;i>4;bytes[p++]=(encoded2&15)<<4|encoded3>>2;bytes[p++]=(encoded3&3)<<6|encoded4&63}return arraybuffer}})("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")},{}],30:[function(_dereq_,module,exports){(function(global){var BlobBuilder=global.BlobBuilder||global.WebKitBlobBuilder||global.MSBlobBuilder||global.MozBlobBuilder;var blobSupported=function(){try{var a=new Blob(["hi"]);return a.size===2}catch(e){return false}}();var blobSupportsArrayBufferView=blobSupported&&function(){try{var b=new Blob([new Uint8Array([1,2])]);return b.size===2}catch(e){return false}}();var blobBuilderSupported=BlobBuilder&&BlobBuilder.prototype.append&&BlobBuilder.prototype.getBlob;function mapArrayBufferViews(ary){for(var i=0;i=55296&&value<=56319&&counter65535){value-=65536;output+=stringFromCharCode(value>>>10&1023|55296);value=56320|value&1023}output+=stringFromCharCode(value)}return output}function checkScalarValue(codePoint){if(codePoint>=55296&&codePoint<=57343){throw Error("Lone surrogate U+"+codePoint.toString(16).toUpperCase()+" is not a scalar value")
+}}function createByte(codePoint,shift){return stringFromCharCode(codePoint>>shift&63|128)}function encodeCodePoint(codePoint){if((codePoint&4294967168)==0){return stringFromCharCode(codePoint)}var symbol="";if((codePoint&4294965248)==0){symbol=stringFromCharCode(codePoint>>6&31|192)}else if((codePoint&4294901760)==0){checkScalarValue(codePoint);symbol=stringFromCharCode(codePoint>>12&15|224);symbol+=createByte(codePoint,6)}else if((codePoint&4292870144)==0){symbol=stringFromCharCode(codePoint>>18&7|240);symbol+=createByte(codePoint,12);symbol+=createByte(codePoint,6)}symbol+=stringFromCharCode(codePoint&63|128);return symbol}function utf8encode(string){var codePoints=ucs2decode(string);var length=codePoints.length;var index=-1;var codePoint;var byteString="";while(++index=byteCount){throw Error("Invalid byte index")}var continuationByte=byteArray[byteIndex]&255;byteIndex++;if((continuationByte&192)==128){return continuationByte&63}throw Error("Invalid continuation byte")}function decodeSymbol(){var byte1;var byte2;var byte3;var byte4;var codePoint;if(byteIndex>byteCount){throw Error("Invalid byte index")}if(byteIndex==byteCount){return false}byte1=byteArray[byteIndex]&255;byteIndex++;if((byte1&128)==0){return byte1}if((byte1&224)==192){var byte2=readContinuationByte();codePoint=(byte1&31)<<6|byte2;if(codePoint>=128){return codePoint}else{throw Error("Invalid continuation byte")}}if((byte1&240)==224){byte2=readContinuationByte();byte3=readContinuationByte();codePoint=(byte1&15)<<12|byte2<<6|byte3;if(codePoint>=2048){checkScalarValue(codePoint);return codePoint}else{throw Error("Invalid continuation byte")}}if((byte1&248)==240){byte2=readContinuationByte();byte3=readContinuationByte();byte4=readContinuationByte();codePoint=(byte1&15)<<18|byte2<<12|byte3<<6|byte4;if(codePoint>=65536&&codePoint<=1114111){return codePoint}}throw Error("Invalid UTF-8 detected")}var byteArray;var byteCount;var byteIndex;function utf8decode(byteString){byteArray=ucs2decode(byteString);byteCount=byteArray.length;byteIndex=0;var codePoints=[];var tmp;while((tmp=decodeSymbol())!==false){codePoints.push(tmp)}return ucs2encode(codePoints)}var utf8={version:"2.0.0",encode:utf8encode,decode:utf8decode};if(typeof define=="function"&&typeof define.amd=="object"&&define.amd){define(function(){return utf8})}else if(freeExports&&!freeExports.nodeType){if(freeModule){freeModule.exports=utf8}else{var object={};var hasOwnProperty=object.hasOwnProperty;for(var key in utf8){hasOwnProperty.call(utf8,key)&&(freeExports[key]=utf8[key])}}}else{root.utf8=utf8}})(this)}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{}],32:[function(_dereq_,module,exports){(function(global){var rvalidchars=/^[\],:{}\s]*$/;var rvalidescape=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;var rvalidtokens=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;var rvalidbraces=/(?:^|:|,)(?:\s*\[)+/g;var rtrimLeft=/^\s+/;var rtrimRight=/\s+$/;module.exports=function parsejson(data){if("string"!=typeof data||!data){return null}data=data.replace(rtrimLeft,"").replace(rtrimRight,"");if(global.JSON&&JSON.parse){return JSON.parse(data)}if(rvalidchars.test(data.replace(rvalidescape,"@").replace(rvalidtokens,"]").replace(rvalidbraces,""))){return new Function("return "+data)()}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{}],33:[function(_dereq_,module,exports){exports.encode=function(obj){var str="";for(var i in obj){if(obj.hasOwnProperty(i)){if(str.length)str+="&";str+=encodeURIComponent(i)+"="+encodeURIComponent(obj[i])}}return str};exports.decode=function(qs){var qry={};var pairs=qs.split("&");for(var i=0,l=pairs.length;i1)))/4)-floor((year-1901+month)/100)+floor((year-1601+month)/400)}}if(!(isProperty={}.hasOwnProperty)){isProperty=function(property){var members={},constructor;if((members.__proto__=null,members.__proto__={toString:1},members).toString!=getClass){isProperty=function(property){var original=this.__proto__,result=property in(this.__proto__=null,this);this.__proto__=original;return result}}else{constructor=members.constructor;isProperty=function(property){var parent=(this.constructor||constructor).prototype;return property in this&&!(property in parent&&this[property]===parent[property])}}members=null;return isProperty.call(this,property)}}var PrimitiveTypes={"boolean":1,number:1,string:1,undefined:1};var isHostType=function(object,property){var type=typeof object[property];return type=="object"?!!object[property]:!PrimitiveTypes[type]};forEach=function(object,callback){var size=0,Properties,members,property;(Properties=function(){this.valueOf=0}).prototype.valueOf=0;members=new Properties;for(property in members){if(isProperty.call(members,property)){size++}}Properties=members=null;if(!size){members=["valueOf","toString","toLocaleString","propertyIsEnumerable","isPrototypeOf","hasOwnProperty","constructor"];forEach=function(object,callback){var isFunction=getClass.call(object)==functionClass,property,length;var hasProperty=!isFunction&&typeof object.constructor!="function"&&isHostType(object,"hasOwnProperty")?object.hasOwnProperty:isProperty;for(property in object){if(!(isFunction&&property=="prototype")&&hasProperty.call(object,property)){callback(property)}}for(length=members.length;property=members[--length];hasProperty.call(object,property)&&callback(property));}}else if(size==2){forEach=function(object,callback){var members={},isFunction=getClass.call(object)==functionClass,property;for(property in object){if(!(isFunction&&property=="prototype")&&!isProperty.call(members,property)&&(members[property]=1)&&isProperty.call(object,property)){callback(property)}}}}else{forEach=function(object,callback){var isFunction=getClass.call(object)==functionClass,property,isConstructor;for(property in object){if(!(isFunction&&property=="prototype")&&isProperty.call(object,property)&&!(isConstructor=property==="constructor")){callback(property)}}if(isConstructor||isProperty.call(object,property="constructor")){callback(property)}}}return forEach(object,callback)};if(!has("json-stringify")){var Escapes={92:"\\\\",34:'\\"',8:"\\b",12:"\\f",10:"\\n",13:"\\r",9:"\\t"};var leadingZeroes="000000";var toPaddedString=function(width,value){return(leadingZeroes+(value||0)).slice(-width)};var unicodePrefix="\\u00";var quote=function(value){var result='"',index=0,length=value.length,isLarge=length>10&&charIndexBuggy,symbols;if(isLarge){symbols=value.split("")}for(;index-1/0&&value<1/0){if(getDay){date=floor(value/864e5);for(year=floor(date/365.2425)+1970-1;getDay(year+1,0)<=date;year++);for(month=floor((date-getDay(year,0))/30.42);getDay(year,month+1)<=date;month++);date=1+date-getDay(year,month);time=(value%864e5+864e5)%864e5;hours=floor(time/36e5)%24;minutes=floor(time/6e4)%60;seconds=floor(time/1e3)%60;milliseconds=time%1e3}else{year=value.getUTCFullYear();month=value.getUTCMonth();date=value.getUTCDate();hours=value.getUTCHours();minutes=value.getUTCMinutes();seconds=value.getUTCSeconds();milliseconds=value.getUTCMilliseconds()}value=(year<=0||year>=1e4?(year<0?"-":"+")+toPaddedString(6,year<0?-year:year):toPaddedString(4,year))+"-"+toPaddedString(2,month+1)+"-"+toPaddedString(2,date)+"T"+toPaddedString(2,hours)+":"+toPaddedString(2,minutes)+":"+toPaddedString(2,seconds)+"."+toPaddedString(3,milliseconds)+"Z"}else{value=null}}else if(typeof value.toJSON=="function"&&(className!=numberClass&&className!=stringClass&&className!=arrayClass||isProperty.call(value,"toJSON"))){value=value.toJSON(property)}}if(callback){value=callback.call(object,property,value)}if(value===null){return"null"}className=getClass.call(value);if(className==booleanClass){return""+value}else if(className==numberClass){return value>-1/0&&value<1/0?""+value:"null"}else if(className==stringClass){return quote(""+value)}if(typeof value=="object"){for(length=stack.length;length--;){if(stack[length]===value){throw TypeError()}}stack.push(value);results=[];prefix=indentation;indentation+=whitespace;if(className==arrayClass){for(index=0,length=value.length;index0){for(whitespace="",width>10&&(width=10);whitespace.length=48&&charCode<=57||charCode>=97&&charCode<=102||charCode>=65&&charCode<=70)){abort()}}value+=fromCharCode("0x"+source.slice(begin,Index));break;default:abort()}}else{if(charCode==34){break}charCode=source.charCodeAt(Index);begin=Index;while(charCode>=32&&charCode!=92&&charCode!=34){charCode=source.charCodeAt(++Index)}value+=source.slice(begin,Index)}}if(source.charCodeAt(Index)==34){Index++;return value}abort();default:begin=Index;if(charCode==45){isSigned=true;charCode=source.charCodeAt(++Index)}if(charCode>=48&&charCode<=57){if(charCode==48&&(charCode=source.charCodeAt(Index+1),charCode>=48&&charCode<=57)){abort()}isSigned=false;for(;Index=48&&charCode<=57);Index++);if(source.charCodeAt(Index)==46){position=++Index;for(;position=48&&charCode<=57);position++);if(position==Index){abort()}Index=position}charCode=source.charCodeAt(Index);if(charCode==101||charCode==69){charCode=source.charCodeAt(++Index);if(charCode==43||charCode==45){Index++}for(position=Index;position=48&&charCode<=57);position++);if(position==Index){abort()}Index=position}return+source.slice(begin,Index)}if(isSigned){abort()}if(source.slice(Index,Index+4)=="true"){Index+=4;return true}else if(source.slice(Index,Index+5)=="false"){Index+=5;return false}else if(source.slice(Index,Index+4)=="null"){Index+=4;return null}abort()}}return"$"};var get=function(value){var results,hasMembers;if(value=="$"){abort()}if(typeof value=="string"){if((charIndexBuggy?value.charAt(0):value[0])=="@"){return value.slice(1)}if(value=="["){results=[];for(;;hasMembers||(hasMembers=true)){value=lex();if(value=="]"){break}if(hasMembers){if(value==","){value=lex();if(value=="]"){abort()}}else{abort()}}if(value==","){abort()}results.push(get(value))}return results}else if(value=="{"){results={};for(;;hasMembers||(hasMembers=true)){value=lex();if(value=="}"){break}if(hasMembers){if(value==","){value=lex();if(value=="}"){abort()}}else{abort()}}if(value==","||typeof value!="string"||(charIndexBuggy?value.charAt(0):value[0])!="@"||lex()!=":"){abort()}results[value.slice(1)]=get(lex())}return results}abort()}return value};var update=function(source,property,callback){var element=walk(source,property,callback);if(element===undef){delete source[property]}else{source[property]=element}};var walk=function(source,property,callback){var value=source[property],length;if(typeof value=="object"&&value){if(getClass.call(value)==arrayClass){for(length=value.length;length--;){update(value,length,callback)}}else{forEach(value,function(property){update(value,property,callback)})}}return callback.call(source,property,value)};JSON3.parse=function(source,callback){var result,value;Index=0;Source=""+source;result=get(lex());if(lex()!="$"){abort()}Index=Source=null;return callback&&getClass.call(callback)==functionClass?walk((value={},value[""]=result,value),"",callback):result}}}if(isLoader){define(function(){return JSON3})}})(this)},{}],48:[function(_dereq_,module,exports){module.exports=toArray;function toArray(list,index){var array=[];index=index||0;for(var i=index||0;i 0 {
if main.RemoteAddrHeaders == nil {
- main.RemoteAddrHeaders = make(map[string]bool, 0)
+ main.RemoteAddrHeaders = make(map[string]bool)
}
for key, value := range v {
main.RemoteAddrHeaders[key] = value
@@ -593,7 +593,7 @@ func WithConfiguration(c Configuration) Configurator {
if v := c.Other; len(v) > 0 {
if main.Other == nil {
- main.Other = make(map[string]interface{}, 0)
+ main.Other = make(map[string]interface{})
}
for key, value := range v {
main.Other[key] = value
@@ -625,6 +625,6 @@ func DefaultConfiguration() Configuration {
"CF-Connecting-IP": false,
},
EnableOptimizations: false,
- Other: make(map[string]interface{}, 0),
+ Other: make(map[string]interface{}),
}
}
diff --git a/context.go b/context.go
index b428ab83..96d001a2 100644
--- a/context.go
+++ b/context.go
@@ -6,6 +6,7 @@ import (
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/host"
"github.com/kataras/iris/core/router"
+ "github.com/kataras/iris/mvc"
)
// TODO: When go 1.9 will be released
@@ -16,9 +17,6 @@ import (
// core/host/supervisor.go
// context.go
// _examples/hello-world/main_go19.go
-// _examples/routing/mvc/controllers/index_go19.go
-// _examples/routing/mvc/controllers/user_go19.go
-// _examples/routing/mvc/main_go19.go
// _examples/tutorial/mvc-from-scratch/README.md
type (
// Context is the midle-man server's "object" for the clients.
@@ -59,6 +57,7 @@ type (
//
// A shortcut for the `core/router#Party`, useful when `PartyFunc` is being used.
Party = router.Party
+
// Controller is the base controller for the high level controllers instances.
//
// This base controller is used as an alternative way of building
@@ -118,11 +117,11 @@ type (
//
// Look `core/router#APIBuilder#Controller` method too.
//
- // A shortcut for the `core/router#Controller`,
+ // A shortcut for the `mvc#Controller`,
// useful when `app.Controller` is being used.
//
// A Controller can be declared by importing
- // the "github.com/kataras/iris/core/router"
+ // the "github.com/kataras/iris/mvc"
// package for machines that have not installed go1.9 yet.
- Controller = router.Controller
+ Controller = mvc.Controller
)
diff --git a/context/context.go b/context/context.go
index 2aff0e6b..9d007182 100644
--- a/context/context.go
+++ b/context/context.go
@@ -887,8 +887,7 @@ func (ctx *context) Request() *http.Request {
// It's used by the router, developers may use that
// to replace and execute handlers immediately.
func (ctx *context) Do(handlers Handlers) {
- ctx.handlers = handlers
- ctx.handlers[0](ctx)
+ Do(ctx, handlers)
}
// AddHandler can add handler(s)
@@ -1453,6 +1452,7 @@ func (ctx *context) Write(rawBody []byte) (int, error) {
//
// Returns the number of bytes written and any write error encountered.
func (ctx *context) Writef(format string, a ...interface{}) (n int, err error) {
+ ctx.ContentType(contentTextHeaderValue)
return ctx.writer.Writef(format, a...)
}
diff --git a/context/gzip_response_writer.go b/context/gzip_response_writer.go
index 6a44c63d..f29c78d1 100644
--- a/context/gzip_response_writer.go
+++ b/context/gzip_response_writer.go
@@ -1,6 +1,7 @@
package context
import (
+ "fmt"
"io"
"sync"
@@ -50,6 +51,11 @@ func releaseGzipWriter(gzipWriter *gzip.Writer) {
func writeGzip(w io.Writer, b []byte) (int, error) {
gzipWriter := acquireGzipWriter(w)
n, err := gzipWriter.Write(b)
+ if err != nil {
+ releaseGzipWriter(gzipWriter)
+ return -1, err
+ }
+ err = gzipWriter.Flush()
releaseGzipWriter(gzipWriter)
return n, err
}
@@ -64,7 +70,6 @@ func AcquireGzipResponseWriter() *GzipResponseWriter {
}
func releaseGzipResponseWriter(w *GzipResponseWriter) {
- releaseGzipWriter(w.gzipWriter)
gzpool.Put(w)
}
@@ -74,9 +79,8 @@ func releaseGzipResponseWriter(w *GzipResponseWriter) {
// went wrong with the response, and write http errors in plain form instead.
type GzipResponseWriter struct {
ResponseWriter
- gzipWriter *gzip.Writer
- chunks []byte
- disabled bool
+ chunks []byte
+ disabled bool
}
var _ ResponseWriter = &GzipResponseWriter{}
@@ -87,7 +91,7 @@ var _ ResponseWriter = &GzipResponseWriter{}
// to change the response writer type.
func (w *GzipResponseWriter) BeginGzipResponse(underline ResponseWriter) {
w.ResponseWriter = underline
- w.gzipWriter = acquireGzipWriter(w.ResponseWriter)
+
w.chunks = w.chunks[0:0]
w.disabled = false
}
@@ -107,6 +111,19 @@ func (w *GzipResponseWriter) Write(contents []byte) (int, error) {
return len(w.chunks), nil
}
+// Writef formats according to a format specifier and writes to the response.
+//
+// Returns the number of bytes written and any write error encountered.
+func (w *GzipResponseWriter) Writef(format string, a ...interface{}) (n int, err error) {
+ return fmt.Fprintf(w, format, a...)
+}
+
+// WriteString prepares the string data write to the gzip writer and finally to its
+// underline response writer, returns the uncompressed len(contents).
+func (w *GzipResponseWriter) WriteString(s string) (int, error) {
+ return w.Write([]byte(s))
+}
+
// WriteNow compresses and writes that data to the underline response writer,
// returns the compressed written len.
//
@@ -116,17 +133,35 @@ func (w *GzipResponseWriter) Write(contents []byte) (int, error) {
// after that, so that information is not closed to the handler anymore.
func (w *GzipResponseWriter) WriteNow(contents []byte) (int, error) {
if w.disabled {
+ // type noOp struct{}
+ //
+ // func (n noOp) Write([]byte) (int, error) {
+ // return 0, nil
+ // }
+ //
+ // var noop = noOp{}
+ // problem solved with w.gzipWriter.Reset(noop):
+ //
+ // the below Write called multiple times but not from here,
+ // the gzip writer does something to the writer, even if we don't call the
+ // w.gzipWriter.Write it does call the underline http.ResponseWriter
+ // multiple times, and therefore it changes the content-length
+ // the problem that results to the #723.
+ //
+ // Or a better idea, acquire and adapt the gzip writer on-time when is not disabled.
+ // So that is not needed any more:
+ // w.gzipWriter.Reset(noop)
return w.ResponseWriter.Write(contents)
}
w.ResponseWriter.Header().Add(varyHeaderKey, "Accept-Encoding")
w.ResponseWriter.Header().Set(contentEncodingHeaderKey, "gzip")
+
// if not `WriteNow` but "Content-Length" header
// is exists, then delete it before `.Write`
// Content-Length should not be there.
// no, for now at least: w.ResponseWriter.Header().Del(contentLengthHeaderKey)
-
- return w.gzipWriter.Write(contents)
+ return writeGzip(w.ResponseWriter, contents)
}
// FlushResponse validates the response headers in order to be compatible with the gzip written data
diff --git a/core/router/api_builder.go b/core/router/api_builder.go
index 28c1bc07..2631df86 100644
--- a/core/router/api_builder.go
+++ b/core/router/api_builder.go
@@ -10,11 +10,12 @@ import (
"github.com/kataras/iris/context"
"github.com/kataras/iris/core/errors"
"github.com/kataras/iris/core/router/macro"
+ "github.com/kataras/iris/mvc/activator"
)
const (
// MethodNone is a Virtual method
- // to store the "offline" routes
+ // to store the "offline" routes.
MethodNone = "NONE"
)
@@ -172,6 +173,36 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co
return r
}
+// HandleMany works like `Handle` but can receive more than one
+// paths separated by spaces and returns always a slice of *Route instead of a single instance of Route.
+//
+// It's useful only if the same handler can handle more than one request paths,
+// otherwise use `Party` which can handle many paths with different handlers and middlewares.
+//
+// Usage:
+// app.HandleMany(iris.MethodGet, "/user /user/{id:int} /user/me", userHandler)
+// At the other side, with `Handle` we've had to write:
+// app.Handle(iris.MethodGet, "/user", userHandler)
+// app.Handle(iris.MethodGet, "/user/{id:int}", userByIDHandler)
+// app.Handle(iris.MethodGet, "/user/me", userMeHandler)
+//
+// This method is used behind the scenes at the `Controller` function
+// in order to handle more than one paths for the same controller instance.
+func (api *APIBuilder) HandleMany(method string, relativePath string, handlers ...context.Handler) (routes []*Route) {
+ trimmedPath := strings.Trim(relativePath, " ")
+ // at least slash
+ // a space
+ // at least one other slash for the next path
+ // app.Controller("/user /user{id}", new(UserController))
+ paths := strings.Split(trimmedPath, " ")
+ for _, p := range paths {
+ if p != "" {
+ routes = append(routes, api.Handle(method, p, handlers...))
+ }
+ }
+ return
+}
+
// Party is just a group joiner of routes which have the same prefix and share same middleware(s) also.
// Party could also be named as 'Join' or 'Node' or 'Group' , Party chosen because it is fun.
func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) Party {
@@ -400,15 +431,13 @@ func (api *APIBuilder) Trace(relativePath string, handlers ...context.Handler) *
// Any registers a route for ALL of the http methods
// (Get,Post,Put,Head,Patch,Options,Connect,Delete).
-func (api *APIBuilder) Any(relativePath string, handlers ...context.Handler) []*Route {
- routes := make([]*Route, len(AllMethods), len(AllMethods))
-
- for i, k := range AllMethods {
- r := api.Handle(k, relativePath, handlers...)
- routes[i] = r
+func (api *APIBuilder) Any(relativePath string, handlers ...context.Handler) (routes []*Route) {
+ for _, m := range AllMethods {
+ r := api.HandleMany(m, relativePath, handlers...)
+ routes = append(routes, r...)
}
- return routes
+ return
}
// Controller registers a `Controller` instance and returns the registered Routes.
@@ -418,11 +447,15 @@ func (api *APIBuilder) Any(relativePath string, handlers ...context.Handler) []*
// It's just an alternative way of building an API for a specific
// path, the controller can register all type of http methods.
//
-// Keep note that this method is a bit slow
+// Keep note that controllers are bit slow
// because of the reflection use however it's as fast as possible because
// it does preparation before the serve-time handler but still
// remains slower than the low-level handlers
-// such as `Handle, Get, Post, Put, Delete, Connect, Head, Trace, Patch` .
+// such as `Handle, Get, Post, Put, Delete, Connect, Head, Trace, Patch`.
+//
+//
+// All fields that are tagged with iris:"persistence"` or binded
+// are being persistence and kept the same between the different requests.
//
// An Example Controller can be:
//
@@ -439,38 +472,53 @@ func (api *APIBuilder) Any(relativePath string, handlers ...context.Handler) []*
// Usage: app.Controller("/", new(IndexController))
//
//
-// Another example with persistence data:
+// Another example with bind:
//
// type UserController struct {
// Controller
//
-// CreatedAt time.Time `iris:"persistence"`
-// Title string `iris:"persistence"`
-// DB *DB `iris:"persistence"`
+// DB *DB
+// CreatedAt time.Time
+//
// }
//
// // Get serves using the User controller when HTTP Method is "GET".
// func (c *UserController) Get() {
// c.Tmpl = "user/index.html"
-// c.Data["title"] = c.Title
+// c.Data["title"] = "User Page"
// c.Data["username"] = "kataras " + c.Params.Get("userid")
// c.Data["connstring"] = c.DB.Connstring
// c.Data["uptime"] = time.Now().Sub(c.CreatedAt).Seconds()
// }
//
-// Usage: app.Controller("/user/{id:int}", &UserController{
-// CreatedAt: time.Now(),
-// Title: "User page",
-// DB: yourDB,
-// })
+// Usage: app.Controller("/user/{id:int}", new(UserController), db, time.Now())
//
-// Read more at `router#Controller`.
-func (api *APIBuilder) Controller(relativePath string, controller interface{}) []*Route {
- routes, err := registerController(api, relativePath, controller)
+// Read more at `/mvc#Controller`.
+func (api *APIBuilder) Controller(relativePath string, controller activator.BaseController,
+ bindValues ...interface{}) (routes []*Route) {
+ registerFunc := func(method string, handler context.Handler) {
+ if method == "ANY" || method == "ALL" {
+ routes = api.Any(relativePath, handler)
+ } else {
+ routes = append(routes, api.HandleMany(method, relativePath, handler)...)
+ }
+ }
+
+ // bind any values to the controller's relative fields
+ // and set them on each new request controller,
+ // binder is an alternative method
+ // of the persistence data control which requires the
+ // user already set the values manually to controller's fields
+ // and tag them with `iris:"persistence"`.
+ //
+ // don't worry it will never be handled if empty values.
+ err := activator.Register(controller, bindValues, nil, registerFunc)
+
if err != nil {
api.reporter.Add("%v for path: '%s'", err, relativePath)
}
- return routes
+
+ return
}
// StaticCacheDuration expiration duration for INACTIVE file handlers, it's the only one global configuration
diff --git a/core/router/controller.go b/core/router/controller.go
deleted file mode 100644
index 4d89f78a..00000000
--- a/core/router/controller.go
+++ /dev/null
@@ -1,290 +0,0 @@
-package router
-
-import (
- "reflect"
- "strings"
-
- "github.com/kataras/iris/context"
- "github.com/kataras/iris/core/errors"
-)
-
-// Controller is the base controller for the high level controllers instances.
-//
-// This base controller is used as an alternative way of building
-// APIs, the controller can register all type of http methods.
-//
-// Keep note that controllers are bit slow
-// because of the reflection use however it's as fast as possible because
-// it does preparation before the serve-time handler but still
-// remains slower than the low-level handlers
-// such as `Handle, Get, Post, Put, Delete, Connect, Head, Trace, Patch`.
-//
-//
-// All fields that are tagged with iris:"persistence"`
-// are being persistence and kept between the different requests,
-// meaning that these data will not be reset-ed on each new request,
-// they will be the same for all requests.
-//
-// An Example Controller can be:
-//
-// type IndexController struct {
-// Controller
-// }
-//
-// func (c *IndexController) Get() {
-// c.Tmpl = "index.html"
-// c.Data["title"] = "Index page"
-// c.Data["message"] = "Hello world!"
-// }
-//
-// Usage: app.Controller("/", new(IndexController))
-//
-//
-// Another example with persistence data:
-//
-// type UserController struct {
-// Controller
-//
-// CreatedAt time.Time `iris:"persistence"`
-// Title string `iris:"persistence"`
-// DB *DB `iris:"persistence"`
-// }
-//
-// // Get serves using the User controller when HTTP Method is "GET".
-// func (c *UserController) Get() {
-// c.Tmpl = "user/index.html"
-// c.Data["title"] = c.Title
-// c.Data["username"] = "kataras " + c.Params.Get("userid")
-// c.Data["connstring"] = c.DB.Connstring
-// c.Data["uptime"] = time.Now().Sub(c.CreatedAt).Seconds()
-// }
-//
-// Usage: app.Controller("/user/{id:int}", &UserController{
-// CreatedAt: time.Now(),
-// Title: "User page",
-// DB: yourDB,
-// })
-//
-// Look `router#APIBuilder#Controller` method too.
-type Controller struct {
- // path params.
- Params *context.RequestParams
-
- // view properties.
- Layout string
- Tmpl string
- Data map[string]interface{}
-
- // give access to the request context itself.
- Ctx context.Context
-}
-
-// all lowercase, so user can see only the fields
-// that are necessary to him/her, do not confuse that
-// with the optional custom `Init` of the higher-level Controller.
-func (b *Controller) init(ctx context.Context) {
- b.Ctx = ctx
- b.Params = ctx.Params()
- b.Data = make(map[string]interface{}, 0)
-}
-
-func (b *Controller) exec() {
- if v := b.Tmpl; v != "" {
- if l := b.Layout; l != "" {
- b.Ctx.ViewLayout(l)
- }
- if d := b.Data; d != nil {
- for key, value := range d {
- b.Ctx.ViewData(key, value)
- }
- }
- b.Ctx.View(v)
- }
-}
-
-var (
- // ErrInvalidControllerType is a static error which fired from `Controller` when
- // the passed "c" instnace is not a valid type of `Controller`.
- ErrInvalidControllerType = errors.New("controller should have a field of Controller type")
-)
-
-// get the field name at compile-time,
-// will help us to catch any unexpected results on future versions.
-var baseControllerName = reflect.TypeOf(Controller{}).Name()
-
-// registers a controller to a specific `Party`.
-// Consumed by `APIBuilder#Controller` function.
-func registerController(p Party, path string, c interface{}) ([]*Route, error) {
- typ := reflect.TypeOf(c)
-
- if typ.Kind() != reflect.Ptr {
- typ = reflect.PtrTo(typ)
- }
-
- elem := typ.Elem()
-
- // check if "c" has the "Controller" typeof `Controller` field.
- b, has := elem.FieldByName(baseControllerName)
- if !has {
- return nil, ErrInvalidControllerType
- }
-
- baseControllerFieldIndex := b.Index[0]
- persistenceFields := make(map[int]reflect.Value, 0)
-
- if numField := elem.NumField(); numField > 1 {
- val := reflect.Indirect(reflect.ValueOf(c))
-
- for i := 0; i < numField; i++ {
- f := elem.Field(i)
- valF := val.Field(i)
- // catch persistence data by tags, i.e:
- // MyData string `iris:"persistence"`
- if t, ok := f.Tag.Lookup("iris"); ok {
- if t == "persistence" {
- persistenceFields[i] = reflect.ValueOf(valF.Interface())
- continue
- }
- }
-
- // no: , lets have only the tag
- // even for pointers, this will make
- // things clear
- // so a *Session can be declared
- // without having to introduce
- // a new tag such as `iris:"omit_persistence"`
- // old:
- // catch persistence data by pointer, i.e:
- // DB *Database
- // if f.Type.Kind() == reflect.Ptr {
- // if !valF.IsNil() {
- // persistenceFields[i] = reflect.ValueOf(valF.Interface())
- // }
- // }
- }
- }
-
- customInitFuncIndex, _ := getCustomFuncIndex(typ, customInitFuncNames...)
- customEndFuncIndex, _ := getCustomFuncIndex(typ, customEndFuncNames...)
-
- // check if has Any() or All()
- // if yes, then register all http methods and
- // exit.
- m, has := typ.MethodByName("Any")
- if !has {
- m, has = typ.MethodByName("All")
- }
- if has {
- routes := p.Any(path,
- controllerToHandler(elem, persistenceFields,
- baseControllerFieldIndex, m.Index, customInitFuncIndex, customEndFuncIndex))
- return routes, nil
- }
-
- var routes []*Route
- // else search the entire controller
- // for any compatible method function
- // and register that.
- for _, method := range AllMethods {
- httpMethodFuncName := strings.Title(strings.ToLower(method))
-
- m, has := typ.MethodByName(httpMethodFuncName)
- if !has {
- continue
- }
-
- httpMethodIndex := m.Index
-
- r := p.Handle(method, path,
- controllerToHandler(elem, persistenceFields,
- baseControllerFieldIndex, httpMethodIndex, customInitFuncIndex, customEndFuncIndex))
- routes = append(routes, r)
- }
- return routes, nil
-}
-
-func controllerToHandler(elem reflect.Type, persistenceFields map[int]reflect.Value,
- baseControllerFieldIndex, httpMethodIndex, customInitFuncIndex, customEndFuncIndex int) context.Handler {
- return func(ctx context.Context) {
- // create a new controller instance of that type(>ptr).
- c := reflect.New(elem)
-
- // get the responsible method.
- // Remember:
- // To improve the performance
- // we don't compare the ctx.Method()[HTTP Method]
- // to the instance's Method, each handler is registered
- // to a specific http method.
- methodFunc := c.Method(httpMethodIndex)
-
- // get the Controller embedded field.
- b, _ := c.Elem().Field(baseControllerFieldIndex).Addr().Interface().(*Controller)
-
- if len(persistenceFields) > 0 {
- elem := c.Elem()
- for index, value := range persistenceFields {
- elem.Field(index).Set(value)
- }
- }
-
- // init the new controller instance.
- b.init(ctx)
-
- // call the higher "Init/BeginRequest(ctx context.Context)",
- // if exists.
- if customInitFuncIndex >= 0 {
- callCustomFuncHandler(ctx, c, customInitFuncIndex)
- }
-
- // if custom Init didn't stop the execution of the
- // context
- if !ctx.IsStopped() {
- // execute the responsible method for that handler.
- methodFunc.Interface().(func())()
- }
-
- if !ctx.IsStopped() {
- // call the higher "Done/EndRequest(ctx context.Context)",
- // if exists.
- if customEndFuncIndex >= 0 {
- callCustomFuncHandler(ctx, c, customEndFuncIndex)
- }
- }
-
- // finally, execute the controller.
- b.exec()
- }
-}
-
-// Useful when more than one methods are using the same
-// request data.
-var (
- // customInitFuncNames can be used as custom functions
- // to init the new instance of controller
- // that is created on each new request.
- // One of these is valid, no both.
- customInitFuncNames = []string{"Init", "BeginRequest"}
- // customEndFuncNames can be used as custom functions
- // to action when the method handler has been finished,
- // this is the last step before server send the response to the client.
- // One of these is valid, no both.
- customEndFuncNames = []string{"Done", "EndRequest"}
-)
-
-func getCustomFuncIndex(typ reflect.Type, funcNames ...string) (initFuncIndex int, has bool) {
- for _, customInitFuncName := range funcNames {
- if m, has := typ.MethodByName(customInitFuncName); has {
- return m.Index, has
- }
- }
-
- return -1, false
-}
-
-// the "cServeTime" is a new "c" instance
-// which is being used at serve time, inside the Handler.
-// it calls the custom function (can be "Init", "BeginRequest", "End" and "EndRequest"),
-// the check of this function made at build time, so it's a safe a call.
-func callCustomFuncHandler(ctx context.Context, cServeTime reflect.Value, initFuncIndex int) {
- cServeTime.Method(initFuncIndex).Interface().(func(ctx context.Context))(ctx)
-}
diff --git a/core/router/controller_test.go b/core/router/controller_test.go
deleted file mode 100644
index 1b084a69..00000000
--- a/core/router/controller_test.go
+++ /dev/null
@@ -1,152 +0,0 @@
-// black-box testing
-package router_test
-
-import (
- "testing"
-
- "github.com/kataras/iris"
- "github.com/kataras/iris/context"
- "github.com/kataras/iris/core/router"
-
- "github.com/kataras/iris/httptest"
-)
-
-type testController struct {
- router.Controller
-}
-
-var writeMethod = func(c router.Controller) {
- c.Ctx.Writef(c.Ctx.Method())
-}
-
-func (c *testController) Get() {
- writeMethod(c.Controller)
-}
-func (c *testController) Post() {
- writeMethod(c.Controller)
-}
-func (c *testController) Put() {
- writeMethod(c.Controller)
-}
-func (c *testController) Delete() {
- writeMethod(c.Controller)
-}
-func (c *testController) Connect() {
- writeMethod(c.Controller)
-}
-func (c *testController) Head() {
- writeMethod(c.Controller)
-}
-func (c *testController) Patch() {
- writeMethod(c.Controller)
-}
-func (c *testController) Options() {
- writeMethod(c.Controller)
-}
-func (c *testController) Trace() {
- writeMethod(c.Controller)
-}
-
-type (
- testControllerAll struct{ router.Controller }
- testControllerAny struct{ router.Controller } // exactly same as All
-)
-
-func (c *testControllerAll) All() {
- writeMethod(c.Controller)
-}
-
-func (c *testControllerAny) All() {
- writeMethod(c.Controller)
-}
-
-func TestControllerMethodFuncs(t *testing.T) {
- app := iris.New()
- app.Controller("/", new(testController))
- app.Controller("/all", new(testControllerAll))
- app.Controller("/any", new(testControllerAny))
-
- e := httptest.New(t, app)
- for _, method := range router.AllMethods {
-
- e.Request(method, "/").Expect().Status(httptest.StatusOK).
- Body().Equal(method)
-
- e.Request(method, "/all").Expect().Status(httptest.StatusOK).
- Body().Equal(method)
-
- e.Request(method, "/any").Expect().Status(httptest.StatusOK).
- Body().Equal(method)
- }
-}
-
-type testControllerPersistence struct {
- router.Controller
- Data string `iris:"persistence"`
-}
-
-func (t *testControllerPersistence) Get() {
- t.Ctx.WriteString(t.Data)
-}
-
-func TestControllerPersistenceFields(t *testing.T) {
- data := "this remains the same for all requests"
- app := iris.New()
- app.Controller("/", &testControllerPersistence{Data: data})
- e := httptest.New(t, app)
- e.GET("/").Expect().Status(httptest.StatusOK).
- Body().Equal(data)
-}
-
-type testControllerBeginAndEndRequestFunc struct {
- router.Controller
-
- Username string
-}
-
-// called before of every method (Get() or Post()).
-//
-// useful when more than one methods using the
-// same request values or context's function calls.
-func (t *testControllerBeginAndEndRequestFunc) BeginRequest(ctx context.Context) {
- t.Username = ctx.Params().Get("username")
- // or t.Params.Get("username") because the
- // t.Ctx == ctx and is being initialized before this "BeginRequest"
-}
-
-// called after every method (Get() or Post()).
-func (t *testControllerBeginAndEndRequestFunc) EndRequest(ctx context.Context) {
- ctx.Writef("done") // append "done" to the response
-}
-
-func (t *testControllerBeginAndEndRequestFunc) Get() {
- t.Ctx.Writef(t.Username)
-}
-
-func (t *testControllerBeginAndEndRequestFunc) Post() {
- t.Ctx.Writef(t.Username)
-}
-
-func TestControllerBeginAndEndRequestFunc(t *testing.T) {
- app := iris.New()
- app.Controller("/profile/{username}", new(testControllerBeginAndEndRequestFunc))
-
- e := httptest.New(t, app)
- usernames := []string{
- "kataras",
- "makis",
- "efi",
- "rg",
- "bill",
- "whoisyourdaddy",
- }
- doneResponse := "done"
-
- for _, username := range usernames {
- e.GET("/profile/" + username).Expect().Status(httptest.StatusOK).
- Body().Equal(username + doneResponse)
- e.POST("/profile/" + username).Expect().Status(httptest.StatusOK).
- Body().Equal(username + doneResponse)
- }
-
-}
diff --git a/core/router/party.go b/core/router/party.go
index 4e4c42f4..283b6817 100644
--- a/core/router/party.go
+++ b/core/router/party.go
@@ -2,6 +2,7 @@ package router
import (
"github.com/kataras/iris/context"
+ "github.com/kataras/iris/mvc/activator"
)
// Party is here to separate the concept of
@@ -51,6 +52,22 @@ type Party interface {
//
// Returns the read-only route information.
Handle(method string, registeredPath string, handlers ...context.Handler) *Route
+ // HandleMany works like `Handle` but can receive more than one
+ // paths separated by spaces and returns always a slice of *Route instead of a single instance of Route.
+ //
+ // It's useful only if the same handler can handle more than one request paths,
+ // otherwise use `Party` which can handle many paths with different handlers and middlewares.
+ //
+ // Usage:
+ // app.HandleMany(iris.MethodGet, "/user /user/{id:int} /user/me", userHandler)
+ // At the other side, with `Handle` we've had to write:
+ // app.Handle(iris.MethodGet, "/user", userHandler)
+ // app.Handle(iris.MethodGet, "/user/{id:int}", userByIDHandler)
+ // app.Handle(iris.MethodGet, "/user/me", userMeHandler)
+ //
+ // This method is used behind the scenes at the `Controller` function
+ // in order to handle more than one paths for the same controller instance.
+ HandleMany(method string, relativePath string, handlers ...context.Handler) []*Route
// None registers an "offline" route
// see context.ExecRoute(routeName) and
@@ -107,11 +124,15 @@ type Party interface {
// It's just an alternative way of building an API for a specific
// path, the controller can register all type of http methods.
//
- // Keep note that this method is a bit slow
+ // Keep note that controllers are bit slow
// because of the reflection use however it's as fast as possible because
// it does preparation before the serve-time handler but still
// remains slower than the low-level handlers
- // such as `Handle, Get, Post, Put, Delete, Connect, Head, Trace, Patch` .
+ // such as `Handle, Get, Post, Put, Delete, Connect, Head, Trace, Patch`.
+ //
+ //
+ // All fields that are tagged with iris:"persistence"` or binded
+ // are being persistence and kept the same between the different requests.
//
// An Example Controller can be:
//
@@ -128,33 +149,29 @@ type Party interface {
// Usage: app.Controller("/", new(IndexController))
//
//
- // Another example with persistence data:
+ // Another example with bind:
//
// type UserController struct {
// Controller
//
- // CreatedAt time.Time `iris:"persistence"`
- // Title string `iris:"persistence"`
- // DB *DB `iris:"persistence"`
+ // DB *DB
+ // CreatedAt time.Time
+ //
// }
//
// // Get serves using the User controller when HTTP Method is "GET".
// func (c *UserController) Get() {
// c.Tmpl = "user/index.html"
- // c.Data["title"] = c.Title
+ // c.Data["title"] = "User Page"
// c.Data["username"] = "kataras " + c.Params.Get("userid")
// c.Data["connstring"] = c.DB.Connstring
// c.Data["uptime"] = time.Now().Sub(c.CreatedAt).Seconds()
// }
//
- // Usage: app.Controller("/user/{id:int}", &UserController{
- // CreatedAt: time.Now(),
- // Title: "User page",
- // DB: yourDB,
- // })
+ // Usage: app.Controller("/user/{id:int}", new(UserController), db, time.Now())
//
- // Read more at `router#Controller`.
- Controller(relativePath string, controller interface{}) []*Route
+ // Read more at `/mvc#Controller`.
+ Controller(relativePath string, controller activator.BaseController, bindValues ...interface{}) []*Route
// StaticHandler returns a new Handler which is ready
// to serve all kind of static files.
diff --git a/core/router/status.go b/core/router/status.go
index 8da0d4a4..9909dc35 100644
--- a/core/router/status.go
+++ b/core/router/status.go
@@ -39,6 +39,7 @@ func (ch *ErrorCodeHandler) Fire(ctx context.Context) {
return
}
}
+
// ctx.StopExecution() // not uncomment this, is here to remember why to.
// note for me: I don't stopping the execution of the other handlers
// because may the user want to add a fallback error code
diff --git a/doc.go b/doc.go
index d5532561..890be75f 100644
--- a/doc.go
+++ b/doc.go
@@ -35,7 +35,7 @@ Source code and other details for the project are available at GitHub:
Current Version
-8.2.6
+8.3.0
Installation
@@ -680,11 +680,108 @@ Example code:
}
-Controllers
+MVC - Model View Controller
-It's very easy to get started, the only function you need to call
-instead of `app.Get/Post/Put/Delete/Connect/Head/Patch/Options/Trace`
-is the `app.Controller`.
+Iris has first-class support for the MVC pattern, you'll not find
+these stuff anywhere else in the Go world.
+
+Example Code
+
+ package main
+
+ import (
+ "github.com/kataras/iris"
+ "github.com/kataras/iris/mvc"
+
+ "github.com/kataras/iris/middleware/logger"
+ "github.com/kataras/iris/middleware/recover"
+ )
+
+ // This example is equivalent to the
+ // https://github.com/kataras/iris/blob/master/_examples/hello-world/main.go
+ //
+ // It seems that additional code you
+ // have to write doesn't worth it
+ // but remember that, this example
+ // does not make use of iris mvc features like
+ // the Model, Persistence or the View engine neither the Session,
+ // it's very simple for learning purposes,
+ // probably you'll never use such
+ // as simple controller anywhere in your app.
+
+ func main() {
+ app := iris.New()
+ // Optionally, add two built'n handlers
+ // that can recover from any http-relative panics
+ // and log the requests to the terminal.
+ app.Use(recover.New())
+ app.Use(logger.New())
+
+ app.Controller("/", new(IndexController))
+ app.Controller("/ping", new(PingController))
+ app.Controller("/hello", new(HelloController))
+
+ // http://localhost:8080
+ // http://localhost:8080/ping
+ // http://localhost:8080/hello
+ app.Run(iris.Addr(":8080"))
+ }
+
+ // IndexController serves the "/".
+ type IndexController struct {
+ // if you build with go1.9 you can omit the import of mvc package
+ // and just use `iris.Controller` instead.
+ mvc.Controller
+ }
+
+ // Get serves
+ // Method: GET
+ // Resource: http://localhost:8080/
+ func (c *IndexController) Get() {
+ c.Ctx.HTML("Welcome!")
+ }
+
+ // PingController serves the "/ping".
+ type PingController struct {
+ mvc.Controller
+ }
+
+ // Get serves
+ // Method: GET
+ // Resource: http://context:8080/ping
+ func (c *PingController) Get() {
+ c.Ctx.WriteString("pong")
+ }
+
+ // HelloController serves the "/hello".
+ type HelloController struct {
+ mvc.Controller
+ }
+
+ // Get serves
+ // Method: GET
+ // Resource: http://localhost:8080/hello
+ func (c *HelloController) Get() {
+ c.Ctx.JSON(iris.Map{"message": "Hello iris web framework."})
+ }
+
+ // Can use more than one, the factory will make sure
+ // that the correct http methods are being registered for each route
+ // for this controller, uncomment these if you want:
+
+ // func (c *HelloController) Post() {}
+ // func (c *HelloController) Put() {}
+ // func (c *HelloController) Delete() {}
+ // func (c *HelloController) Connect() {}
+ // func (c *HelloController) Head() {}
+ // func (c *HelloController) Patch() {}
+ // func (c *HelloController) Options() {}
+ // func (c *HelloController) Trace() {}
+ // or All() or Any() to catch all http methods.
+
+
+Iris web framework supports Request data, Models, Persistence Data and Binding
+with the fastest possible execution.
Characteristics:
@@ -693,9 +790,14 @@ then the controller should have a function named `Get()`,
you can define more than one method function to serve in the same Controller struct.
Persistence data inside your Controller struct (share data between requests)
-via `iris:"persistence"` tag right to the field.
+via `iris:"persistence"` tag right to the field or Bind using `app.Controller("/" , new(myController), theBindValue)`.
-Access to the request path parameters via the `Params` field.
+Models inside your Controller struct (set-ed at the Method function and rendered by the View)
+via `iris:"model"` tag right to the field, i.e User UserModel `iris:"model" name:"user"`
+view will recognise it as `{{.user}}`.
+If `name` tag is missing then it takes the field's name, in this case the `"User"`.
+
+Access to the request path and its parameters via the `Path and Params` fields.
Access to the template file that should be rendered via the `Tmpl` field.
@@ -709,65 +811,31 @@ Access to the low-level `context.Context` via the `Ctx` field.
Flow as you used to, `Controllers` can be registered to any `Party`,
including Subdomains, the Party's begin and done handlers work as expected.
-Optional `Init(ctx) or BeginRequest(ctx)` function to perform any initialization before the methods,
+Optional `BeginRequest(ctx)` function to perform any initialization before the method execution,
useful to call middlewares or when many methods use the same collection of data.
-Optional `Done(ctx) or EndRequest(ctx)` function to perform any finalization after the methods executed.
+Optional `EndRequest(ctx)` function to perform any finalization after any method executed.
-Example Code:
+Inheritance, see for example our `mvc.SessionController`, it has the `mvc.Controller` as an embedded field
+and it adds its logic to its `BeginRequest`. Source file: https://github.com/kataras/iris/blob/master/mvc/session_controller.go.
+Using Iris MVC for code reuse
- // file: main.go
+By creating components that are independent of one another,
+developers are able to reuse components quickly and easily in other applications.
+The same (or similar) view for one application can be refactored for another application with
+different data because the view is simply handling how the data is being displayed to the user.
- package main
+If you're new to back-end web development read about the MVC architectural pattern first,
+a good start is that wikipedia article: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller.
- import (
- "github.com/kataras/iris"
+Follow the examples below,
- "controllers"
- )
+- Hello world: https://github.com/kataras/iris/blob/master/_examples/mvc/hello-world/main.go
- func main() {
- app := iris.New()
- app.RegisterView(iris.HTML("./views", ".html"))
+- Session Controller usage: https://github.com/kataras/iris/blob/master/_examples/mvc/session-controller/main.go
- app.Controller("/", new(controllers.Index))
-
- // http://localhost:8080/
- app.Run(iris.Addr(":8080"))
- }
-
-
- // file: controllers/index.go
-
- package controllers
-
- import (
- "github.com/kataras/iris/core/router"
- )
-
- // Index is our index example controller.
- type Index struct {
- router.Controller
- // if you're using go1.9:
- // you can omit the /core/router import statement
- // and just use the `iris.Controller` instead.
- }
-
- // will handle GET method on http://localhost:8080/
- func (c *Index) Get() {
- c.Tmpl = "index.html"
- c.Data["title"] = "Index page"
- c.Data["message"] = "Hello world!"
- }
-
- // will handle POST method on http://localhost:8080/
- func (c *Index) Post() {}
-
-
-Tip: declare a func(c *Index) All() {} or Any() to register all HTTP Methods.
-
-A full example can be found at: https://github.com/kataras/iris/tree/master/_examples/routing/mvc.
+- A simple but featured Controller with model and views: https://github.com/kataras/iris/tree/master/_examples/mvc/controller-with-model-and-view
Parameterized Path
diff --git a/faq.md b/faq.md
new file mode 100644
index 00000000..a53d75d6
--- /dev/null
+++ b/faq.md
@@ -0,0 +1,38 @@
+## How to upgrade
+
+```sh
+go get -u github.com/kataras/iris
+```
+
+## Active development mode
+
+Many ideas to implement but no breaking changes.
+
+https://github.com/kataras/iris/issues/722
+
+## Can I found a job if I learn how to use Iris?
+
+Yes, not only because you will learn Golang in the same time, but there are some positions
+open for Iris-specific developers the time we speak.
+
+- https://glints.id/opportunities/jobs/5553
+
+## Can Iris be used in production after Dubai purchase?
+
+Yes, now more than ever.
+
+https://github.com/kataras/iris/issues/711
+
+## Do we have a community Chat?
+
+Yes, https://kataras.rocket.chat/channel/iris.
+
+https://github.com/kataras/iris/issues/646
+
+## How this open-source project still active and shine?
+
+By normal people like you, who help us by donating small or larger amounts of money.
+
+Help this project to continue deliver awesome and unique features with the higher code quality as possible by donating any amount.
+
+[![](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=kataras2006%40hotmail%2ecom&lc=GR&item_name=Iris%20web%20framework&item_number=iriswebframeworkdonationid2016¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted)
\ No newline at end of file
diff --git a/iris.go b/iris.go
index a3233b95..f115c248 100644
--- a/iris.go
+++ b/iris.go
@@ -32,7 +32,7 @@ import (
const (
// Version is the current version number of the Iris Web Framework.
- Version = "8.2.6"
+ Version = "8.3.0"
)
// HTTP status codes as registered with IANA.
@@ -383,7 +383,7 @@ func (app *Application) NewHost(srv *http.Server) *host.Supervisor {
if srv.Addr == "" {
srv.Addr = ":8080"
}
- app.logger.Debugf("HTTP Server Addr: %s", srv.Addr)
+ app.logger.Debugf("Host: addr is %s", srv.Addr)
// create the new host supervisor
// bind the constructed server and return it
@@ -399,25 +399,25 @@ func (app *Application) NewHost(srv *http.Server) *host.Supervisor {
app.config.vhost = netutil.ResolveVHost(srv.Addr)
}
- app.logger.Debugf("VHost: %s", app.config.vhost)
+ app.logger.Debugf("Host: virtual host is %s", app.config.vhost)
// the below schedules some tasks that will run among the server
if !app.config.DisableStartupLog {
// show the available info to exit from app.
su.RegisterOnServe(host.WriteStartupLogOnServe(app.logger.Printer.Output)) // app.logger.Writer -> Info
- app.logger.Debugf("Host: Register startup notifier")
+ app.logger.Debugf("Host: register startup notifier")
}
if !app.config.DisableInterruptHandler {
// when CTRL+C/CMD+C pressed.
shutdownTimeout := 5 * time.Second
host.RegisterOnInterrupt(host.ShutdownOnInterrupt(su, shutdownTimeout))
- app.logger.Debugf("Host: Register server shutdown on interrupt(CTRL+C/CMD+C)")
+ app.logger.Debugf("Host: register server shutdown on interrupt(CTRL+C/CMD+C)")
}
su.IgnoredErrors = append(su.IgnoredErrors, app.config.IgnoreServerErrors...)
- app.logger.Debugf("Host: Server will ignore the following errors: %s", su.IgnoredErrors)
+ app.logger.Debugf("Host: server will ignore the following errors: %s", su.IgnoredErrors)
su.Configure(app.hostConfigurators...)
app.Hosts = append(app.Hosts, su)
@@ -608,7 +608,7 @@ func (app *Application) Build() error {
}
if app.view.Len() > 0 {
- app.logger.Debugf("%d registered view engine(s)", app.view.Len())
+ app.logger.Debugf("Application: %d registered view engine(s)", app.view.Len())
// view engine
// here is where we declare the closed-relative framework functions.
// Each engine has their defaults, i.e yield,render,render_r,partial, params...
@@ -651,7 +651,7 @@ func (app *Application) Run(serve Runner, withOrWithout ...Configurator) error {
}
app.Configure(withOrWithout...)
- app.logger.Debugf("Application: running using %d host(s)", len(app.Hosts)+1)
+ app.logger.Debugf("Application: running using %d host(s)", len(app.Hosts)+1)
if !app.config.DisableVersionChecker && app.logger.Level != golog.DisableLevel {
go CheckVersion()
diff --git a/mvc/activator/activator.go b/mvc/activator/activator.go
new file mode 100644
index 00000000..914535fb
--- /dev/null
+++ b/mvc/activator/activator.go
@@ -0,0 +1,312 @@
+package activator
+
+import (
+ "reflect"
+
+ "github.com/kataras/golog"
+
+ "github.com/kataras/iris/context"
+ "github.com/kataras/iris/core/errors"
+)
+
+type (
+ // TController is the type of the controller,
+ // it contains all the necessary information to load
+ // and serve the controller to the outside world,
+ // think it as a "supervisor" of your Controller which
+ // cares about you.
+ TController struct {
+ // the type of the user/dev's "c" controller (interface{})
+ Type reflect.Type
+ // it's the first passed value of the controller instance,
+ // we need this to collect and save the persistence fields' values.
+ Value reflect.Value
+
+ binder *binder // executed even before the BeginRequest if not nil.
+
+ controls []TControl // executed on request, after the BeginRequest and before the EndRequest.
+
+ // the actual method functions
+ // i.e for "GET" it's the `Get()`
+ //
+ // Here we have a strange relation by-design.
+ // It contains the methods
+ // but we have different handlers
+ // for each of these methods,
+ // while in the same time all of these
+ // are depend from this TypeInfo struct.
+ // So we have TypeInfo -> Methods -> Each(TypeInfo, Method.Index)
+ // -> Handler for X HTTPMethod, see `Register`.
+ Methods []MethodFunc
+ }
+ // MethodFunc is part of the `TController`,
+ // it contains the index for a specific http method,
+ // taken from user's controller struct.
+ MethodFunc struct {
+ Index int
+ HTTPMethod string
+ }
+)
+
+// ErrControlSkip never shows up, used to determinate
+// if a control's Load return error is critical or not,
+// `ErrControlSkip` means that activation can continue
+// and skip this control.
+var ErrControlSkip = errors.New("skip control")
+
+// TControl is an optional feature that an app can benefit
+// by using its own custom controls to control the flow
+// inside a controller, they are being registered per controller.
+//
+// Naming:
+// I could find better name such as 'Control',
+// but I can imagine the user's confusion about `Controller`
+// and `Control` types, they are different but they may
+// use that as embedded, so it can not start with the world "C..".
+// The best name that shows the relation between this
+// and the controller type info struct(TController) is the "TControl",
+// `TController` is prepended with "T" for the same reasons, it's different
+// than `Controller`, the TController is the "description" of the user's
+// `Controller` embedded field.
+type TControl interface { // or CoreControl?
+ // Load should returns nil if its `Handle`
+ // should be called on serve time.
+ //
+ // if error is filled then controller info
+ // is not created and that error is returned to the
+ // high-level caller, but the `ErrControlSkip` can be used
+ // to skip the control without breaking the rest of the registration.
+ Load(t *TController) error
+ // Handle executes the control.
+ // It accepts the context, the new controller instance
+ // and the specific methodFunc based on the request.
+ Handle(ctx context.Context, controller reflect.Value, methodFunc func())
+}
+
+func isControlErr(err error) bool {
+ if err != nil {
+ if isSkipper(err) {
+ return false
+ }
+ return true
+ }
+
+ return false
+}
+
+func isSkipper(err error) bool {
+ if err != nil {
+ if err.Error() == ErrControlSkip.Error() {
+ return true
+ }
+ }
+ return false
+}
+
+// the parent package should complete this "interface"
+// it's not exported, so their functions
+// but reflect doesn't care about it, so we are ok
+// to compare the type of the base controller field
+// with this "ctrl", see `buildTypeInfo` and `buildMethodHandler`.
+
+var (
+ // ErrMissingControllerInstance is a static error which fired from `Controller` when
+ // the passed "c" instnace is not a valid type of `Controller`.
+ ErrMissingControllerInstance = errors.New("controller should have a field of Controller type")
+ // ErrInvalidControllerType fired when the "Controller" field is not
+ // the correct type.
+ ErrInvalidControllerType = errors.New("controller instance is not a valid implementation")
+)
+
+// BaseController is the controller interface,
+// which the main request `Controller` will implement automatically.
+// End-User doesn't need to have any knowledge of this if she/he doesn't want to implement
+// a new Controller type.
+type BaseController interface {
+ BeginRequest(ctx context.Context)
+ EndRequest(ctx context.Context)
+}
+
+// ActivateController returns a new controller type info description.
+// A TController is not useful for the end-developer
+// but it can be used for debugging.
+func ActivateController(base BaseController, bindValues []interface{},
+ controls []TControl) (TController, error) {
+
+ // get and save the type.
+ typ := reflect.TypeOf(base)
+ if typ.Kind() != reflect.Ptr {
+ typ = reflect.PtrTo(typ)
+ }
+
+ // first instance value, needed to validate
+ // the actual type of the controller field
+ // and to collect and save the instance's persistence fields'
+ // values later on.
+ val := reflect.Indirect(reflect.ValueOf(base))
+ ctrlName := val.Type().Name()
+
+ // set the binder, can be nil this check at made at runtime.
+ binder := newBinder(typ.Elem(), bindValues)
+ if binder != nil {
+ for _, bf := range binder.fields {
+ golog.Debugf("MVC %s: binder loaded for '%s' with field index of: %d",
+ ctrlName, bf.Name, bf.Index)
+ }
+ }
+
+ t := TController{
+ Type: typ,
+ Value: val,
+ binder: binder,
+ }
+
+ // first the custom controls,
+ // after these, the persistence,
+ // the method control
+ // which can set the model and
+ // last the model control.
+ controls = append(controls, []TControl{
+ // PersistenceDataControl stores the optional data
+ // that will be shared among all requests.
+ PersistenceDataControl(),
+ // MethodControl is the actual method function
+ // i.e for "GET" it's the `Get()` that will be
+ // fired.
+ MethodControl(),
+ // ModelControl stores the optional models from
+ // the struct's fields values that
+ // are being setted by the method function
+ // and set them as ViewData.
+ ModelControl()}...)
+
+ for _, control := range controls {
+ err := control.Load(&t)
+ // fail on first control error if not ErrControlSkip.
+ if isControlErr(err) {
+ return t, err
+ }
+
+ if isSkipper(err) {
+ continue
+ }
+
+ golog.Debugf("MVC %s: succeed load of the %#v", ctrlName, control)
+ t.controls = append(t.controls, control)
+ }
+
+ return t, nil
+}
+
+// builds the handler for a type based on the method index (i.e Get() -> [0], Post() -> [1]).
+func buildMethodHandler(t TController, methodFuncIndex int) context.Handler {
+ elem := t.Type.Elem()
+ /*
+ // good idea, it speeds up the whole thing by ~1MB per 20MB at my personal
+ // laptop but this way the Model for example which is not a persistence
+ // variable can stay for the next request
+ // (if pointer receiver but if not then variables like `Tmpl` cannot stay)
+ // and that will have unexpected results.
+ // however we keep it here I want to see it every day in order to find a better way.
+
+ type runtimeC struct {
+ method func()
+ c reflect.Value
+ elem reflect.Value
+ b BaseController
+ }
+
+ pool := sync.Pool{
+ New: func() interface{} {
+
+ c := reflect.New(elem)
+ methodFunc := c.Method(methodFuncIndex).Interface().(func())
+ b, _ := c.Interface().(BaseController)
+
+ elem := c.Elem()
+ if t.binder != nil {
+ t.binder.handle(elem)
+ }
+
+ rc := runtimeC{
+ c: c,
+ elem: elem,
+ b: b,
+ method: methodFunc,
+ }
+ return rc
+ },
+ }
+ */
+
+ return func(ctx context.Context) {
+ // // create a new controller instance of that type(>ptr).
+ c := reflect.New(elem)
+
+ if t.binder != nil {
+ t.binder.handle(c)
+ if ctx.IsStopped() {
+ return
+ }
+ }
+
+ // get the Controller embedded field's addr.
+ // it should never be invalid here because we made that checks on activation.
+ // but if somone tries to "crack" that, then just stop the world in order to be notified,
+ // we don't want to go away from that type of mistake.
+ b := c.Interface().(BaseController)
+
+ // init the request.
+ b.BeginRequest(ctx)
+
+ methodFunc := c.Method(methodFuncIndex).Interface().(func())
+ // execute the controls by order, including the method control.
+ for _, control := range t.controls {
+ if ctx.IsStopped() {
+ break
+ }
+ control.Handle(ctx, c, methodFunc)
+ }
+
+ // finally, execute the controller, don't check for IsStopped.
+ b.EndRequest(ctx)
+ }
+}
+
+// RegisterFunc used by the caller to register the result routes.
+type RegisterFunc func(httpMethod string, handler context.Handler)
+
+// RegisterMethodHandlers receives a `TController`, description of the
+// user's controller, and calls the "registerFunc" for each of its
+// method handlers.
+//
+// Not useful for the end-developer, but may needed for debugging
+// at the future.
+func RegisterMethodHandlers(t TController, registerFunc RegisterFunc) {
+ // range over the type info's method funcs,
+ // build a new handler for each of these
+ // methods and register them to their
+ // http methods using the registerFunc, which is
+ // responsible to convert these into routes
+ // and add them to router via the APIBuilder.
+ for _, m := range t.Methods {
+ registerFunc(m.HTTPMethod, buildMethodHandler(t, m.Index))
+ }
+}
+
+// Register receives a "controller",
+// a pointer of an instance which embeds the `Controller`,
+// the value of "baseControllerFieldName" should be `Controller`
+// if embedded and "controls" that can intercept on controller
+// activation and on the controller's handler, at serve-time.
+func Register(controller BaseController, bindValues []interface{}, controls []TControl,
+ registerFunc RegisterFunc) error {
+
+ t, err := ActivateController(controller, bindValues, controls)
+ if err != nil {
+ return err
+ }
+
+ RegisterMethodHandlers(t, registerFunc)
+ return nil
+}
diff --git a/mvc/activator/binder.go b/mvc/activator/binder.go
new file mode 100644
index 00000000..ad641d92
--- /dev/null
+++ b/mvc/activator/binder.go
@@ -0,0 +1,118 @@
+package activator
+
+import (
+ "reflect"
+)
+
+type binder struct {
+ values []interface{}
+ fields []field
+}
+
+// binder accepts a value of something
+// and tries to find its equalivent type
+// inside the controller and sets that to it,
+// after that each new instance of the controller will have
+// this value on the specific field, like persistence data control does.
+//
+// returns a nil binder if values are not valid bindable data to the controller type.
+func newBinder(elemType reflect.Type, values []interface{}) *binder {
+ if len(values) == 0 {
+ return nil
+ }
+
+ b := &binder{values: values}
+ b.fields = b.lookup(elemType)
+
+ // if nothing valid found return nil, so the caller
+ // can omit the binder.
+ if len(b.fields) == 0 {
+ return nil
+ }
+
+ return b
+}
+
+func (b *binder) lookup(elem reflect.Type) (fields []field) {
+ for _, v := range b.values {
+ value := reflect.ValueOf(v)
+ for i, n := 0, elem.NumField(); i < n; i++ {
+ elemField := elem.Field(i)
+
+ if elemField.Type == value.Type() {
+ // we area inside the correct type
+ // println("[0] prepare bind filed for " + elemField.Name)
+ fields = append(fields, field{
+ Index: i,
+ Name: elemField.Name,
+ Type: elemField.Type,
+ Value: value,
+ })
+ continue
+ }
+
+ f := lookupStruct(elemField.Type, value)
+ if f != nil {
+ fields = append(fields, field{
+ Index: i,
+ Name: elemField.Name,
+ Type: elemField.Type,
+ embedded: f,
+ })
+ }
+
+ }
+ }
+ return
+}
+
+func lookupStruct(elem reflect.Type, value reflect.Value) *field {
+ // ignore if that field is not a struct
+ if elem.Kind() != reflect.Struct {
+ // and it's not a controller because we don't want to accidentally
+ // set fields to other user fields. Or no?
+ // ||
+ // (elem.Name() != "" && !strings.HasSuffix(elem.Name(), "Controller")) {
+ return nil
+ }
+
+ // search by fields.
+ for i, n := 0, elem.NumField(); i < n; i++ {
+ elemField := elem.Field(i)
+ if elemField.Type == value.Type() {
+ // println("Types are equal of: " + elemField.Type.Name() + " " + elemField.Name + " and " + value.Type().Name())
+ // we area inside the correct type.
+ return &field{
+ Index: i,
+ Name: elemField.Name,
+ Type: elemField.Type,
+ Value: value,
+ }
+ }
+
+ // if field is struct and the value is struct
+ // then try inside its fields for a compatible
+ // field type.
+ if elemField.Type.Kind() == reflect.Struct && value.Type().Kind() == reflect.Struct {
+ elemFieldEmb := elem.Field(i)
+ f := lookupStruct(elemFieldEmb.Type, value)
+ if f != nil {
+ fp := &field{
+ Index: i,
+ Name: elemFieldEmb.Name,
+ Type: elemFieldEmb.Type,
+ embedded: f,
+ }
+ return fp
+ }
+ }
+ }
+ return nil
+}
+
+func (b *binder) handle(c reflect.Value) {
+ elem := c.Elem() // controller should always be a pointer at this state
+ for _, f := range b.fields {
+ f.sendTo(elem)
+ }
+}
diff --git a/mvc/activator/callable_control.go b/mvc/activator/callable_control.go
new file mode 100644
index 00000000..6d4d1c74
--- /dev/null
+++ b/mvc/activator/callable_control.go
@@ -0,0 +1,53 @@
+package activator
+
+import (
+ "reflect"
+
+ "github.com/kataras/iris/context"
+)
+
+func getCustomFuncIndex(t *TController, funcNames ...string) (funcIndex int, has bool) {
+ val := t.Value
+
+ for _, funcName := range funcNames {
+ if m, has := t.Type.MethodByName(funcName); has {
+ if _, isRequestFunc := val.Method(m.Index).Interface().(func(ctx context.Context)); isRequestFunc {
+ return m.Index, has
+ }
+ }
+ }
+
+ return -1, false
+}
+
+type callableControl struct {
+ Functions []string
+ index int
+}
+
+func (cc *callableControl) Load(t *TController) error {
+ funcIndex, has := getCustomFuncIndex(t, cc.Functions...)
+ if !has {
+ return ErrControlSkip
+ }
+
+ cc.index = funcIndex
+ return nil
+}
+
+// the "c" is a new "c" instance
+// which is being used at serve time, inside the Handler.
+// it calls the custom function (can be "Init", "BeginRequest", "End" and "EndRequest"),
+// the check of this function made at build time, so it's a safe a call.
+func (cc *callableControl) Handle(ctx context.Context, c reflect.Value, methodFunc func()) {
+ c.Method(cc.index).Interface().(func(ctx context.Context))(ctx)
+}
+
+// CallableControl is a generic-propose `TControl`
+// which finds one function in the user's controller's struct
+// based on the possible "funcName(s)" and executes
+// that inside the handler, at serve-time, by passing
+// the current request's `iris/context/#Context`.
+func CallableControl(funcName ...string) TControl {
+ return &callableControl{Functions: funcName}
+}
diff --git a/mvc/activator/method_control.go b/mvc/activator/method_control.go
new file mode 100644
index 00000000..45520224
--- /dev/null
+++ b/mvc/activator/method_control.go
@@ -0,0 +1,81 @@
+package activator
+
+import (
+ "reflect"
+ "strings"
+
+ "github.com/kataras/iris/context"
+ "github.com/kataras/iris/core/errors"
+)
+
+var availableMethods = [...]string{
+ "ANY", // will be registered using the `core/router#APIBuilder#Any`
+ "ALL", // same as ANY
+ "NONE", // offline route
+ // valid http methods
+ "GET",
+ "POST",
+ "PUT",
+ "DELETE",
+ "CONNECT",
+ "HEAD",
+ "PATCH",
+ "OPTIONS",
+ "TRACE",
+}
+
+type methodControl struct{}
+
+// ErrMissingHTTPMethodFunc fired when the controller doesn't handle any valid HTTP method.
+var ErrMissingHTTPMethodFunc = errors.New(`controller can not be activated,
+ missing a compatible HTTP method function, i.e Get()`)
+
+func (mc *methodControl) Load(t *TController) error {
+ // search the entire controller
+ // for any compatible method function
+ // and register that.
+ for _, method := range availableMethods {
+ if m, ok := t.Type.MethodByName(getMethodName(method)); ok {
+
+ t.Methods = append(t.Methods, MethodFunc{
+ HTTPMethod: method,
+ Index: m.Index,
+ })
+
+ // check if method was Any() or All()
+ // if yes, then break to skip any conflict with the rest of the method functions.
+ // (this will be registered to all valid http methods by the APIBuilder)
+ if method == "ANY" || method == "ALL" {
+ break
+ }
+ }
+ }
+
+ if len(t.Methods) == 0 {
+ // no compatible method found, fire an error and stop everything.
+ return ErrMissingHTTPMethodFunc
+ }
+
+ return nil
+}
+
+func getMethodName(httpMethod string) string {
+ httpMethodFuncName := strings.Title(strings.ToLower(httpMethod))
+ return httpMethodFuncName
+}
+
+func (mc *methodControl) Handle(ctx context.Context, c reflect.Value, methodFunc func()) {
+ // execute the responsible method for that handler.
+ // Remember:
+ // To improve the performance
+ // we don't compare the ctx.Method()[HTTP Method]
+ // to the instance's Method, each handler is registered
+ // to a specific http method.
+ methodFunc()
+}
+
+// MethodControl loads and serve the main functionality of the controllers,
+// which is to run a function based on the http method (pre-computed).
+func MethodControl() TControl {
+ return &methodControl{}
+}
diff --git a/mvc/activator/model_control.go b/mvc/activator/model_control.go
new file mode 100644
index 00000000..fcb9423c
--- /dev/null
+++ b/mvc/activator/model_control.go
@@ -0,0 +1,54 @@
+package activator
+
+import (
+ "reflect"
+
+ "github.com/kataras/iris/context"
+)
+
+type modelControl struct {
+ fields []field
+}
+
+func (mc *modelControl) Load(t *TController) error {
+ fields := lookupFields(t, func(f reflect.StructField) bool {
+ if tag, ok := f.Tag.Lookup("iris"); ok {
+ if tag == "model" {
+ return true
+ }
+ }
+ return false
+ })
+
+ if len(fields) == 0 {
+ // first is the `Controller` so we need to
+ // check the second and after that.
+ return ErrControlSkip
+ }
+
+ mc.fields = fields
+ return nil
+}
+
+func (mc *modelControl) Handle(ctx context.Context, c reflect.Value, methodFunc func()) {
+ elem := c.Elem() // controller should always be a pointer at this state
+
+ for _, f := range mc.fields {
+ elemField := elem.Field(f.Index)
+ // check if current controller's element field
+ // is valid, is not nil and it's type is the same (should be but make that check to be sure).
+ if !elemField.IsValid() || (elemField.Kind() == reflect.Ptr && elemField.IsNil()) || elemField.Type() != f.Type {
+ continue
+ }
+ fieldValue := elemField.Interface()
+ // fmt.Printf("setting %s to %#v", f.Name, fieldValue)
+ ctx.ViewData(f.Name, fieldValue)
+ }
+}
+
+// ModelControl returns a TControl which is responsible
+// to load and handle the `Model(s)` inside a controller struct
+// via the `iris:"model"` tag field.
+func ModelControl() TControl {
+ return &modelControl{}
+}
diff --git a/mvc/activator/persistence_data_control.go b/mvc/activator/persistence_data_control.go
new file mode 100644
index 00000000..bfa9f2b6
--- /dev/null
+++ b/mvc/activator/persistence_data_control.go
@@ -0,0 +1,103 @@
+package activator
+
+import (
+ "reflect"
+
+ "github.com/kataras/iris/context"
+)
+
+type field struct {
+ Name string // by-defaultis the field's name but if `name: "other"` then it's overridden.
+ Index int
+ Type reflect.Type
+ Value reflect.Value
+
+ embedded *field
+}
+
+func (ff field) sendTo(elem reflect.Value) {
+ if embedded := ff.embedded; embedded != nil {
+ if ff.Index >= 0 {
+ embedded.sendTo(elem.Field(ff.Index))
+ }
+ return
+ }
+ elemField := elem.Field(ff.Index)
+ if elemField.Kind() == reflect.Ptr && !elemField.IsNil() {
+ return
+ }
+
+ elemField.Set(ff.Value)
+}
+
+func lookupFields(t *TController, validator func(reflect.StructField) bool) (fields []field) {
+ elem := t.Type.Elem()
+
+ for i, n := 0, elem.NumField(); i < n; i++ {
+ elemField := elem.Field(i)
+ valF := t.Value.Field(i)
+
+ // catch persistence data by tags, i.e:
+ // MyData string `iris:"persistence"`
+ if validator(elemField) {
+ name := elemField.Name
+ if nameTag, ok := elemField.Tag.Lookup("name"); ok {
+ name = nameTag
+ }
+
+ f := field{
+ Name: name,
+ Index: i,
+ Type: elemField.Type,
+ }
+
+ if valF.IsValid() || (valF.Kind() == reflect.Ptr && !valF.IsNil()) {
+ val := reflect.ValueOf(valF.Interface())
+ if val.IsValid() || (val.Kind() == reflect.Ptr && !val.IsNil()) {
+ f.Value = val
+ }
+ }
+
+ fields = append(fields, f)
+ }
+ }
+ return
+}
+
+type persistenceDataControl struct {
+ fields []field
+}
+
+func (d *persistenceDataControl) Load(t *TController) error {
+ fields := lookupFields(t, func(f reflect.StructField) bool {
+ if tag, ok := f.Tag.Lookup("iris"); ok {
+ if tag == "persistence" {
+ return true
+ }
+ }
+ return false
+ })
+
+ if len(fields) == 0 {
+ // first is the `Controller` so we need to
+ // check the second and after that.
+ return ErrControlSkip
+ }
+
+ d.fields = fields
+ return nil
+}
+
+func (d *persistenceDataControl) Handle(ctx context.Context, c reflect.Value, methodFunc func()) {
+ elem := c.Elem() // controller should always be a pointer at this state
+ for _, f := range d.fields {
+ f.sendTo(elem)
+ }
+}
+
+// PersistenceDataControl loads and re-stores
+// the persistence data by scanning the original
+// `TController.Value` instance of the user's controller.
+func PersistenceDataControl() TControl {
+ return &persistenceDataControl{}
+}
diff --git a/mvc/controller.go b/mvc/controller.go
new file mode 100644
index 00000000..235e1c70
--- /dev/null
+++ b/mvc/controller.go
@@ -0,0 +1,129 @@
+package mvc
+
+import (
+ "github.com/kataras/iris/context"
+ "github.com/kataras/iris/core/memstore"
+ "github.com/kataras/iris/mvc/activator"
+)
+
+// Controller is the base controller for the high level controllers instances.
+//
+// This base controller is used as an alternative way of building
+// APIs, the controller can register all type of http methods.
+//
+// Keep note that controllers are bit slow
+// because of the reflection use however it's as fast as possible because
+// it does preparation before the serve-time handler but still
+// remains slower than the low-level handlers
+// such as `Handle, Get, Post, Put, Delete, Connect, Head, Trace, Patch`.
+//
+//
+// All fields that are tagged with iris:"persistence"` or binded
+// are being persistence and kept the same between the different requests.
+//
+// An Example Controller can be:
+//
+// type IndexController struct {
+// Controller
+// }
+//
+// func (c *IndexController) Get() {
+// c.Tmpl = "index.html"
+// c.Data["title"] = "Index page"
+// c.Data["message"] = "Hello world!"
+// }
+//
+// Usage: app.Controller("/", new(IndexController))
+//
+//
+// Another example with bind:
+//
+// type UserController struct {
+// mvc.Controller
+//
+// DB *DB
+// CreatedAt time.Time
+// }
+//
+// // Get serves using the User controller when HTTP Method is "GET".
+// func (c *UserController) Get() {
+// c.Tmpl = "user/index.html"
+// c.Data["title"] = "User Page"
+// c.Data["username"] = "kataras " + c.Params.Get("userid")
+// c.Data["connstring"] = c.DB.Connstring
+// c.Data["uptime"] = time.Now().Sub(c.CreatedAt).Seconds()
+// }
+//
+// Usage: app.Controller("/user/{id:int}", new(UserController), db, time.Now())
+//
+// Look `core/router/APIBuilder#Controller` method too.
+type Controller struct {
+ // path and path params.
+ Path string
+ Params *context.RequestParams
+
+ // some info read and write,
+ // can be already set-ed by previous handlers as well.
+ Status int
+ Values *memstore.Store
+
+ // view read and write,
+ // can be already set-ed by previous handlers as well.
+ Layout string
+ Tmpl string
+ Data map[string]interface{}
+
+ // give access to the request context itself.
+ Ctx context.Context
+}
+
+// BeginRequest starts the main controller
+// it initialize the Ctx and other fields.
+//
+// End-Developer can ovverride it but it still MUST be called.
+func (c *Controller) BeginRequest(ctx context.Context) {
+ // path and path params
+ c.Path = ctx.Path()
+ c.Params = ctx.Params()
+ // response status code
+ c.Status = ctx.GetStatusCode()
+ // share values
+ c.Values = ctx.Values()
+ // view
+ c.Data = make(map[string]interface{}, 0)
+ // context itself
+ c.Ctx = ctx
+}
+
+// EndRequest is the final method which will be executed
+// before response sent.
+//
+// It checks for the fields and calls the necessary context's
+// methods to modify the response to the client.
+//
+// End-Developer can ovveride it but still should be called at the end.
+func (c *Controller) EndRequest(ctx context.Context) {
+ if path := c.Path; path != "" && path != ctx.Path() {
+ // then redirect
+ ctx.Redirect(path)
+ return
+ }
+
+ if status := c.Status; status > 0 && status != ctx.GetStatusCode() {
+ ctx.StatusCode(status)
+ }
+
+ if view := c.Tmpl; view != "" {
+ if layout := c.Layout; layout != "" {
+ ctx.ViewLayout(layout)
+ }
+ if data := c.Data; data != nil {
+ for k, v := range data {
+ ctx.ViewData(k, v)
+ }
+ }
+ ctx.View(view)
+ }
+}
+
+var _ activator.BaseController = &Controller{}
diff --git a/mvc/controller_test.go b/mvc/controller_test.go
new file mode 100644
index 00000000..dde3d71f
--- /dev/null
+++ b/mvc/controller_test.go
@@ -0,0 +1,284 @@
+// black-box testing
+package mvc_test
+
+import (
+ "testing"
+
+ "github.com/kataras/iris"
+ "github.com/kataras/iris/context"
+ "github.com/kataras/iris/mvc"
+
+ "github.com/kataras/iris/core/router"
+ "github.com/kataras/iris/httptest"
+)
+
+type testController struct {
+ mvc.Controller
+}
+
+var writeMethod = func(c mvc.Controller) {
+ c.Ctx.Writef(c.Ctx.Method())
+}
+
+func (c *testController) Get() {
+ writeMethod(c.Controller)
+}
+func (c *testController) Post() {
+ writeMethod(c.Controller)
+}
+func (c *testController) Put() {
+ writeMethod(c.Controller)
+}
+func (c *testController) Delete() {
+ writeMethod(c.Controller)
+}
+func (c *testController) Connect() {
+ writeMethod(c.Controller)
+}
+func (c *testController) Head() {
+ writeMethod(c.Controller)
+}
+func (c *testController) Patch() {
+ writeMethod(c.Controller)
+}
+func (c *testController) Options() {
+ writeMethod(c.Controller)
+}
+func (c *testController) Trace() {
+ writeMethod(c.Controller)
+}
+
+type (
+ testControllerAll struct{ mvc.Controller }
+ testControllerAny struct{ mvc.Controller } // exactly the same as All
+)
+
+func (c *testControllerAll) All() {
+ writeMethod(c.Controller)
+}
+
+func (c *testControllerAny) Any() {
+ writeMethod(c.Controller)
+}
+
+func TestControllerMethodFuncs(t *testing.T) {
+ app := iris.New()
+ app.Controller("/", new(testController))
+ app.Controller("/all", new(testControllerAll))
+ app.Controller("/any", new(testControllerAny))
+
+ e := httptest.New(t, app)
+ for _, method := range router.AllMethods {
+
+ e.Request(method, "/").Expect().Status(httptest.StatusOK).
+ Body().Equal(method)
+
+ e.Request(method, "/all").Expect().Status(httptest.StatusOK).
+ Body().Equal(method)
+
+ e.Request(method, "/any").Expect().Status(httptest.StatusOK).
+ Body().Equal(method)
+ }
+}
+
+func TestControllerMethodAndPathHandleMany(t *testing.T) {
+ app := iris.New()
+ app.Controller("/ /path1 /path2 /path3", new(testController))
+
+ e := httptest.New(t, app)
+ for _, method := range router.AllMethods {
+
+ e.Request(method, "/").Expect().Status(httptest.StatusOK).
+ Body().Equal(method)
+
+ e.Request(method, "/path1").Expect().Status(httptest.StatusOK).
+ Body().Equal(method)
+
+ e.Request(method, "/path2").Expect().Status(httptest.StatusOK).
+ Body().Equal(method)
+ }
+}
+
+type testControllerPersistence struct {
+ mvc.Controller
+ Data string `iris:"persistence"`
+}
+
+func (t *testControllerPersistence) Get() {
+ t.Ctx.WriteString(t.Data)
+}
+
+func TestControllerPersistenceFields(t *testing.T) {
+ data := "this remains the same for all requests"
+ app := iris.New()
+ app.Controller("/", &testControllerPersistence{Data: data})
+ e := httptest.New(t, app)
+ e.GET("/").Expect().Status(httptest.StatusOK).
+ Body().Equal(data)
+}
+
+type testControllerBeginAndEndRequestFunc struct {
+ mvc.Controller
+
+ Username string
+}
+
+// called before of every method (Get() or Post()).
+//
+// useful when more than one methods using the
+// same request values or context's function calls.
+func (t *testControllerBeginAndEndRequestFunc) BeginRequest(ctx context.Context) {
+ t.Controller.BeginRequest(ctx)
+ t.Username = ctx.Params().Get("username")
+ // or t.Params.Get("username") because the
+ // t.Ctx == ctx and is being initialized at the t.Controller.BeginRequest.
+}
+
+// called after every method (Get() or Post()).
+func (t *testControllerBeginAndEndRequestFunc) EndRequest(ctx context.Context) {
+ ctx.Writef("done") // append "done" to the response
+ t.Controller.EndRequest(ctx)
+}
+
+func (t *testControllerBeginAndEndRequestFunc) Get() {
+ t.Ctx.Writef(t.Username)
+}
+
+func (t *testControllerBeginAndEndRequestFunc) Post() {
+ t.Ctx.Writef(t.Username)
+}
+
+func TestControllerBeginAndEndRequestFunc(t *testing.T) {
+ app := iris.New()
+ app.Controller("/profile/{username}", new(testControllerBeginAndEndRequestFunc))
+
+ e := httptest.New(t, app)
+ usernames := []string{
+ "kataras",
+ "makis",
+ "efi",
+ "rg",
+ "bill",
+ "whoisyourdaddy",
+ }
+ doneResponse := "done"
+
+ for _, username := range usernames {
+ e.GET("/profile/" + username).Expect().Status(httptest.StatusOK).
+ Body().Equal(username + doneResponse)
+ e.POST("/profile/" + username).Expect().Status(httptest.StatusOK).
+ Body().Equal(username + doneResponse)
+ }
+}
+
+type Model struct {
+ Username string
+}
+
+type testControllerModel struct {
+ mvc.Controller
+
+ TestModel Model `iris:"model" name:"myModel"`
+ TestModel2 Model `iris:"model"`
+}
+
+func (t *testControllerModel) Get() {
+ username := t.Ctx.Params().Get("username")
+ t.TestModel = Model{Username: username}
+ t.TestModel2 = Model{Username: username + "2"}
+}
+
+func (t *testControllerModel) EndRequest(ctx context.Context) {
+ // t.Ctx == ctx
+
+ m, ok := t.Ctx.GetViewData()["myModel"]
+ if !ok {
+ t.Ctx.Writef("fail TestModel load and set")
+ return
+ }
+
+ model, ok := m.(Model)
+
+ if !ok {
+ t.Ctx.Writef("fail to override the TestModel name by the tag")
+ return
+ }
+
+ // test without custom name tag, should have the field's nae.
+ m, ok = t.Ctx.GetViewData()["TestModel2"]
+ if !ok {
+ t.Ctx.Writef("fail TestModel2 load and set")
+ return
+ }
+
+ model2, ok := m.(Model)
+
+ if !ok {
+ t.Ctx.Writef("fail to override the TestModel2 name by the tag")
+ return
+ }
+
+ // models are being rendered via the View at ViewData but
+ // we just test it here, so print it back.
+ t.Ctx.Writef(model.Username + model2.Username)
+
+ t.Controller.EndRequest(ctx)
+}
+func TestControllerModel(t *testing.T) {
+ app := iris.New()
+ app.Controller("/model/{username}", new(testControllerModel))
+
+ e := httptest.New(t, app)
+ usernames := []string{
+ "kataras",
+ "makis",
+ }
+
+ for _, username := range usernames {
+ e.GET("/model/" + username).Expect().Status(httptest.StatusOK).
+ Body().Equal(username + username + "2")
+ }
+}
+
+type testBindType struct {
+ title string
+}
+
+type testControllerBindStruct struct {
+ mvc.Controller
+ // should start with upper letter of course
+ TitlePointer *testBindType // should have the value of the "myTitlePtr" on test
+ TitleValue testBindType // should have the value of the "myTitleV" on test
+ Other string // just another type to check the field collection, should be empty
+}
+
+func (t *testControllerBindStruct) Get() {
+ t.Ctx.Writef(t.TitlePointer.title + t.TitleValue.title + t.Other)
+}
+
+type testControllerBindDeep struct {
+ testControllerBindStruct
+}
+
+func (t *testControllerBindDeep) Get() {
+ // t.testControllerBindStruct.Get()
+ t.Ctx.Writef(t.TitlePointer.title + t.TitleValue.title + t.Other)
+}
+func TestControllerBind(t *testing.T) {
+ app := iris.New()
+ t1, t2 := "my pointer title", "val title"
+ // test bind pointer to pointer of the correct type
+ myTitlePtr := &testBindType{title: t1}
+ // test bind value to value of the correct type
+ myTitleV := testBindType{title: t2}
+
+ app.Controller("/", new(testControllerBindStruct), myTitlePtr, myTitleV)
+ app.Controller("/deep", new(testControllerBindDeep), myTitlePtr, myTitleV)
+
+ e := httptest.New(t, app)
+ expected := t1 + t2
+ e.GET("/").Expect().Status(httptest.StatusOK).
+ Body().Equal(expected)
+ e.GET("/deep").Expect().Status(httptest.StatusOK).
+ Body().Equal(expected)
+}
diff --git a/mvc/session_controller.go b/mvc/session_controller.go
new file mode 100644
index 00000000..bc6adbe6
--- /dev/null
+++ b/mvc/session_controller.go
@@ -0,0 +1,35 @@
+package mvc
+
+import (
+ "github.com/kataras/iris/context"
+ "github.com/kataras/iris/sessions"
+)
+
+// SessionController is a simple `Controller` implementation
+// which requires a binded session manager in order to give
+// direct access to the current client's session via its `Session` field.
+type SessionController struct {
+ Controller
+
+ Manager *sessions.Sessions
+ Session *sessions.Session
+}
+
+var managerMissing = "MVC SessionController: session manager field is nil, you have to bind it to a *sessions.Sessions"
+
+// BeginRequest calls the Controller's BeginRequest
+// and tries to initialize the current user's Session.
+func (s *SessionController) BeginRequest(ctx context.Context) {
+ s.Controller.BeginRequest(ctx)
+ if s.Manager == nil {
+ ctx.Application().Logger().Errorf(managerMissing)
+ return
+ }
+
+ s.Session = s.Manager.Start(ctx)
+}
+
+/* TODO:
+Maybe add struct tags on `binder` for required binded values
+in order to log error if some of the bindings are missing or leave that to the end-developers?
+*/
diff --git a/sessions/session.go b/sessions/session.go
index 54bb1d1a..3c1c3518 100644
--- a/sessions/session.go
+++ b/sessions/session.go
@@ -137,6 +137,11 @@ var errFindParse = errors.New("Unable to find the %s with key: %s. Found? %#v")
// GetInt same as Get but returns as int, if not found then returns -1 and an error.
func (s *Session) GetInt(key string) (int, error) {
+ return s.GetIntDefault(key, -1)
+}
+
+// GetIntDefault same as Get but returns as int, if not found then returns the "defaultValue".
+func (s *Session) GetIntDefault(key string, defaultValue int) (int, error) {
v := s.Get(key)
if vint, ok := v.(int); ok {
@@ -147,7 +152,7 @@ func (s *Session) GetInt(key string) (int, error) {
return strconv.Atoi(vstring)
}
- return -1, errFindParse.Format("int", key, v)
+ return defaultValue, errFindParse.Format("int", key, v)
}
// GetInt64 same as Get but returns as int64, if not found then returns -1 and an error.
diff --git a/version.go b/version.go
index e851bbd7..6c73fd61 100644
--- a/version.go
+++ b/version.go
@@ -7,7 +7,6 @@ import (
"net/url"
"os"
"os/exec"
- "strings"
"sync"
"time"
@@ -15,11 +14,6 @@ import (
"github.com/kataras/iris/core/netutil"
)
-const (
- versionURL = "http://iris-go.com/version"
- updateCmd = "go get -u -f -v github.com/kataras/iris"
-)
-
var checkVersionOnce = sync.Once{}
// CheckVersion checks for any available updates.
@@ -36,8 +30,8 @@ type versionInfo struct {
}
func checkVersion() {
- client := netutil.Client(time.Duration(20 * time.Second))
- r, err := client.PostForm(versionURL, url.Values{"current_version": {Version}})
+ client := netutil.Client(20 * time.Second)
+ r, err := client.PostForm("http://iris-go.com/version", url.Values{"current_version": {Version}})
if err != nil {
golog.Debugf("%v", err)
@@ -105,9 +99,8 @@ func checkVersion() {
}
if shouldUpdate {
- goget := strings.Split(updateCmd, " ")
- // go get -u github.com/:owner/:repo
- cmd := exec.Command(goget[0], goget[1:]...)
+ repo := "github.com/kataras/iris"
+ cmd := exec.Command("go", "get", "-u", "-f", "-v", repo)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stdout
@@ -116,6 +109,6 @@ func checkVersion() {
return
}
- golog.Infof("Update process finished.\nManual restart is required to apply the changes...")
+ golog.Infof("Update process finished.\nManual rebuild and restart is required to apply the changes...")
}
}
diff --git a/view/view.go b/view/view.go
index c551fa0a..77894d57 100644
--- a/view/view.go
+++ b/view/view.go
@@ -43,6 +43,12 @@ var (
// ExecuteWriter calls the correct view Engine's ExecuteWriter func
func (v *View) ExecuteWriter(w io.Writer, filename string, layout string, bindingData interface{}) error {
+ if len(filename) > 2 {
+ if filename[0] == '/' { // omit first slash
+ filename = filename[1:]
+ }
+ }
+
e := v.Find(filename)
if e == nil {
return errNoViewEngineForExt.Format(filepath.Ext(filename))