Compare commits

...

7 Commits

Author SHA1 Message Date
Андрей Иванов db142def3f Some fixes 2024-04-24 08:14:50 +03:00
Andrey Ivanov 60bc6071f2 Switch to go mod 2022-08-24 18:37:19 +03:00
m0sth8 02a6525b9e Update README.md 2014-03-07 02:24:51 +03:00
Vyacheslav Bakhmutov bb1fe60e12 add nginx compatibility 2014-03-06 17:54:32 +04:00
Vyacheslav Bakhmutov ea65bfbbc6 small size fix 2014-03-06 17:54:10 +04:00
Vyacheslav Bakhmutov 10287002d1 add minimized libs 2014-03-06 17:53:22 +04:00
Vyacheslav Bakhmutov 062ba637f9 step-8 2014-03-06 16:23:19 +04:00
16 changed files with 925 additions and 103 deletions

View File

@ -0,0 +1,10 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="run" type="GoApplicationRunConfiguration" factoryName="Go Application">
<module name="skimmer" />
<working_directory value="$PROJECT_DIR$" />
<kind value="DIRECTORY" />
<directory value="$PROJECT_DIR$/src" />
<filePath value="$PROJECT_DIR$" />
<method v="2" />
</configuration>
</component>

View File

@ -7,6 +7,7 @@ There is an articale in russian on habrahabr:
- [First chapter](http://habrahabr.ru/post/208680/).
- [Second chapter](http://habrahabr.ru/post/214425/).
Also you can try a [demo](http://skimmer.tulu.la) version online.
In russian
==========
@ -16,3 +17,5 @@ In russian
Первые две части статьи по русски на habrahabr:
- [Первая часть](http://habrahabr.ru/post/208680/).
- [Вторая часть](http://habrahabr.ru/post/214425/).
Так же вы можете попробовать [демо-версию](http://skimmer.tulu.la) онлайн.

14
public/static/lib/angular-route.min.js vendored Normal file
View File

@ -0,0 +1,14 @@
/*
AngularJS v1.2.12
(c) 2010-2014 Google, Inc. http://angularjs.org
License: MIT
*/
(function(h,e,A){'use strict';function u(w,q,k){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,c,b,f,n){function y(){l&&(l.$destroy(),l=null);g&&(k.leave(g),g=null)}function v(){var b=w.current&&w.current.locals;if(e.isDefined(b&&b.$template)){var b=a.$new(),f=w.current;g=n(b,function(d){k.enter(d,null,g||c,function(){!e.isDefined(t)||t&&!a.$eval(t)||q()});y()});l=f.scope=b;l.$emit("$viewContentLoaded");l.$eval(h)}else y()}var l,g,t=b.autoscroll,h=b.onload||"";
a.$on("$routeChangeSuccess",v);v()}}}function z(e,h,k){return{restrict:"ECA",priority:-400,link:function(a,c){var b=k.current,f=b.locals;c.html(f.$template);var n=e(c.contents());b.controller&&(f.$scope=a,f=h(b.controller,f),b.controllerAs&&(a[b.controllerAs]=f),c.data("$ngControllerController",f),c.children().data("$ngControllerController",f));n(a)}}}h=e.module("ngRoute",["ng"]).provider("$route",function(){function h(a,c){return e.extend(new (e.extend(function(){},{prototype:a})),c)}function q(a,
e){var b=e.caseInsensitiveMatch,f={originalPath:a,regexp:a},h=f.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(a,e,b,c){a="?"===c?c:null;c="*"===c?c:null;h.push({name:b,optional:!!a});e=e||"";return""+(a?"":e)+"(?:"+(a?e:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");f.regexp=RegExp("^"+a+"$",b?"i":"");return f}var k={};this.when=function(a,c){k[a]=e.extend({reloadOnSearch:!0},c,a&&q(a,c));if(a){var b="/"==a[a.length-1]?a.substr(0,a.length-
1):a+"/";k[b]=e.extend({redirectTo:a},q(b,c))}return this};this.otherwise=function(a){this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache","$sce",function(a,c,b,f,n,q,v,l){function g(){var d=t(),m=r.current;if(d&&m&&d.$$route===m.$$route&&e.equals(d.pathParams,m.pathParams)&&!d.reloadOnSearch&&!x)m.params=d.params,e.copy(m.params,b),a.$broadcast("$routeUpdate",m);else if(d||m)x=!1,a.$broadcast("$routeChangeStart",d,m),(r.current=
d)&&d.redirectTo&&(e.isString(d.redirectTo)?c.path(u(d.redirectTo,d.params)).search(d.params).replace():c.url(d.redirectTo(d.pathParams,c.path(),c.search())).replace()),f.when(d).then(function(){if(d){var a=e.extend({},d.resolve),c,b;e.forEach(a,function(d,c){a[c]=e.isString(d)?n.get(d):n.invoke(d)});e.isDefined(c=d.template)?e.isFunction(c)&&(c=c(d.params)):e.isDefined(b=d.templateUrl)&&(e.isFunction(b)&&(b=b(d.params)),b=l.getTrustedResourceUrl(b),e.isDefined(b)&&(d.loadedTemplateUrl=b,c=q.get(b,
{cache:v}).then(function(a){return a.data})));e.isDefined(c)&&(a.$template=c);return f.all(a)}}).then(function(c){d==r.current&&(d&&(d.locals=c,e.copy(d.params,b)),a.$broadcast("$routeChangeSuccess",d,m))},function(c){d==r.current&&a.$broadcast("$routeChangeError",d,m,c)})}function t(){var a,b;e.forEach(k,function(f,k){var p;if(p=!b){var s=c.path();p=f.keys;var l={};if(f.regexp)if(s=f.regexp.exec(s)){for(var g=1,q=s.length;g<q;++g){var n=p[g-1],r="string"==typeof s[g]?decodeURIComponent(s[g]):s[g];
n&&r&&(l[n.name]=r)}p=l}else p=null;else p=null;p=a=p}p&&(b=h(f,{params:e.extend({},c.search(),a),pathParams:a}),b.$$route=f)});return b||k[null]&&h(k[null],{params:{},pathParams:{}})}function u(a,c){var b=[];e.forEach((a||"").split(":"),function(a,d){if(0===d)b.push(a);else{var e=a.match(/(\w+)(.*)/),f=e[1];b.push(c[f]);b.push(e[2]||"");delete c[f]}});return b.join("")}var x=!1,r={routes:k,reload:function(){x=!0;a.$evalAsync(g)}};a.$on("$locationChangeSuccess",g);return r}]});h.provider("$routeParams",
function(){this.$get=function(){return{}}});h.directive("ngView",u);h.directive("ngView",z);u.$inject=["$route","$anchorScroll","$animate"];z.$inject=["$compile","$controller","$route"]})(window,window.angular);
//# sourceMappingURL=angular-route.min.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

56
public/static/lib/lodash.min.js vendored Normal file
View File

@ -0,0 +1,56 @@
/**
* @license
* Lo-Dash 2.4.1 (Custom Build) lodash.com/license | Underscore.js 1.5.2 underscorejs.org/LICENSE
* Build: `lodash modern -o ./dist/lodash.js`
*/
;(function(){function n(n,t,e){e=(e||0)-1;for(var r=n?n.length:0;++e<r;)if(n[e]===t)return e;return-1}function t(t,e){var r=typeof e;if(t=t.l,"boolean"==r||null==e)return t[e]?0:-1;"number"!=r&&"string"!=r&&(r="object");var u="number"==r?e:m+e;return t=(t=t[r])&&t[u],"object"==r?t&&-1<n(t,e)?0:-1:t?0:-1}function e(n){var t=this.l,e=typeof n;if("boolean"==e||null==n)t[n]=true;else{"number"!=e&&"string"!=e&&(e="object");var r="number"==e?n:m+n,t=t[e]||(t[e]={});"object"==e?(t[r]||(t[r]=[])).push(n):t[r]=true
}}function r(n){return n.charCodeAt(0)}function u(n,t){for(var e=n.m,r=t.m,u=-1,o=e.length;++u<o;){var i=e[u],a=r[u];if(i!==a){if(i>a||typeof i=="undefined")return 1;if(i<a||typeof a=="undefined")return-1}}return n.n-t.n}function o(n){var t=-1,r=n.length,u=n[0],o=n[r/2|0],i=n[r-1];if(u&&typeof u=="object"&&o&&typeof o=="object"&&i&&typeof i=="object")return false;for(u=f(),u["false"]=u["null"]=u["true"]=u.undefined=false,o=f(),o.k=n,o.l=u,o.push=e;++t<r;)o.push(n[t]);return o}function i(n){return"\\"+U[n]
}function a(){return h.pop()||[]}function f(){return g.pop()||{k:null,l:null,m:null,"false":false,n:0,"null":false,number:null,object:null,push:null,string:null,"true":false,undefined:false,o:null}}function l(n){n.length=0,h.length<_&&h.push(n)}function c(n){var t=n.l;t&&c(t),n.k=n.l=n.m=n.object=n.number=n.string=n.o=null,g.length<_&&g.push(n)}function p(n,t,e){t||(t=0),typeof e=="undefined"&&(e=n?n.length:0);var r=-1;e=e-t||0;for(var u=Array(0>e?0:e);++r<e;)u[r]=n[t+r];return u}function s(e){function h(n,t,e){if(!n||!V[typeof n])return n;
t=t&&typeof e=="undefined"?t:tt(t,e,3);for(var r=-1,u=V[typeof n]&&Fe(n),o=u?u.length:0;++r<o&&(e=u[r],false!==t(n[e],e,n)););return n}function g(n,t,e){var r;if(!n||!V[typeof n])return n;t=t&&typeof e=="undefined"?t:tt(t,e,3);for(r in n)if(false===t(n[r],r,n))break;return n}function _(n,t,e){var r,u=n,o=u;if(!u)return o;for(var i=arguments,a=0,f=typeof e=="number"?2:i.length;++a<f;)if((u=i[a])&&V[typeof u])for(var l=-1,c=V[typeof u]&&Fe(u),p=c?c.length:0;++l<p;)r=c[l],"undefined"==typeof o[r]&&(o[r]=u[r]);
return o}function U(n,t,e){var r,u=n,o=u;if(!u)return o;var i=arguments,a=0,f=typeof e=="number"?2:i.length;if(3<f&&"function"==typeof i[f-2])var l=tt(i[--f-1],i[f--],2);else 2<f&&"function"==typeof i[f-1]&&(l=i[--f]);for(;++a<f;)if((u=i[a])&&V[typeof u])for(var c=-1,p=V[typeof u]&&Fe(u),s=p?p.length:0;++c<s;)r=p[c],o[r]=l?l(o[r],u[r]):u[r];return o}function H(n){var t,e=[];if(!n||!V[typeof n])return e;for(t in n)me.call(n,t)&&e.push(t);return e}function J(n){return n&&typeof n=="object"&&!Te(n)&&me.call(n,"__wrapped__")?n:new Q(n)
}function Q(n,t){this.__chain__=!!t,this.__wrapped__=n}function X(n){function t(){if(r){var n=p(r);be.apply(n,arguments)}if(this instanceof t){var o=nt(e.prototype),n=e.apply(o,n||arguments);return wt(n)?n:o}return e.apply(u,n||arguments)}var e=n[0],r=n[2],u=n[4];return $e(t,n),t}function Z(n,t,e,r,u){if(e){var o=e(n);if(typeof o!="undefined")return o}if(!wt(n))return n;var i=ce.call(n);if(!K[i])return n;var f=Ae[i];switch(i){case T:case F:return new f(+n);case W:case P:return new f(n);case z:return o=f(n.source,C.exec(n)),o.lastIndex=n.lastIndex,o
}if(i=Te(n),t){var c=!r;r||(r=a()),u||(u=a());for(var s=r.length;s--;)if(r[s]==n)return u[s];o=i?f(n.length):{}}else o=i?p(n):U({},n);return i&&(me.call(n,"index")&&(o.index=n.index),me.call(n,"input")&&(o.input=n.input)),t?(r.push(n),u.push(o),(i?St:h)(n,function(n,i){o[i]=Z(n,t,e,r,u)}),c&&(l(r),l(u)),o):o}function nt(n){return wt(n)?ke(n):{}}function tt(n,t,e){if(typeof n!="function")return Ut;if(typeof t=="undefined"||!("prototype"in n))return n;var r=n.__bindData__;if(typeof r=="undefined"&&(De.funcNames&&(r=!n.name),r=r||!De.funcDecomp,!r)){var u=ge.call(n);
De.funcNames||(r=!O.test(u)),r||(r=E.test(u),$e(n,r))}if(false===r||true!==r&&1&r[1])return n;switch(e){case 1:return function(e){return n.call(t,e)};case 2:return function(e,r){return n.call(t,e,r)};case 3:return function(e,r,u){return n.call(t,e,r,u)};case 4:return function(e,r,u,o){return n.call(t,e,r,u,o)}}return Mt(n,t)}function et(n){function t(){var n=f?i:this;if(u){var h=p(u);be.apply(h,arguments)}return(o||c)&&(h||(h=p(arguments)),o&&be.apply(h,o),c&&h.length<a)?(r|=16,et([e,s?r:-4&r,h,null,i,a])):(h||(h=arguments),l&&(e=n[v]),this instanceof t?(n=nt(e.prototype),h=e.apply(n,h),wt(h)?h:n):e.apply(n,h))
}var e=n[0],r=n[1],u=n[2],o=n[3],i=n[4],a=n[5],f=1&r,l=2&r,c=4&r,s=8&r,v=e;return $e(t,n),t}function rt(e,r){var u=-1,i=st(),a=e?e.length:0,f=a>=b&&i===n,l=[];if(f){var p=o(r);p?(i=t,r=p):f=false}for(;++u<a;)p=e[u],0>i(r,p)&&l.push(p);return f&&c(r),l}function ut(n,t,e,r){r=(r||0)-1;for(var u=n?n.length:0,o=[];++r<u;){var i=n[r];if(i&&typeof i=="object"&&typeof i.length=="number"&&(Te(i)||yt(i))){t||(i=ut(i,t,e));var a=-1,f=i.length,l=o.length;for(o.length+=f;++a<f;)o[l++]=i[a]}else e||o.push(i)}return o
}function ot(n,t,e,r,u,o){if(e){var i=e(n,t);if(typeof i!="undefined")return!!i}if(n===t)return 0!==n||1/n==1/t;if(n===n&&!(n&&V[typeof n]||t&&V[typeof t]))return false;if(null==n||null==t)return n===t;var f=ce.call(n),c=ce.call(t);if(f==D&&(f=q),c==D&&(c=q),f!=c)return false;switch(f){case T:case F:return+n==+t;case W:return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case z:case P:return n==oe(t)}if(c=f==$,!c){var p=me.call(n,"__wrapped__"),s=me.call(t,"__wrapped__");if(p||s)return ot(p?n.__wrapped__:n,s?t.__wrapped__:t,e,r,u,o);
if(f!=q)return false;if(f=n.constructor,p=t.constructor,f!=p&&!(dt(f)&&f instanceof f&&dt(p)&&p instanceof p)&&"constructor"in n&&"constructor"in t)return false}for(f=!u,u||(u=a()),o||(o=a()),p=u.length;p--;)if(u[p]==n)return o[p]==t;var v=0,i=true;if(u.push(n),o.push(t),c){if(p=n.length,v=t.length,(i=v==p)||r)for(;v--;)if(c=p,s=t[v],r)for(;c--&&!(i=ot(n[c],s,e,r,u,o)););else if(!(i=ot(n[v],s,e,r,u,o)))break}else g(t,function(t,a,f){return me.call(f,a)?(v++,i=me.call(n,a)&&ot(n[a],t,e,r,u,o)):void 0}),i&&!r&&g(n,function(n,t,e){return me.call(e,t)?i=-1<--v:void 0
});return u.pop(),o.pop(),f&&(l(u),l(o)),i}function it(n,t,e,r,u){(Te(t)?St:h)(t,function(t,o){var i,a,f=t,l=n[o];if(t&&((a=Te(t))||Pe(t))){for(f=r.length;f--;)if(i=r[f]==t){l=u[f];break}if(!i){var c;e&&(f=e(l,t),c=typeof f!="undefined")&&(l=f),c||(l=a?Te(l)?l:[]:Pe(l)?l:{}),r.push(t),u.push(l),c||it(l,t,e,r,u)}}else e&&(f=e(l,t),typeof f=="undefined"&&(f=t)),typeof f!="undefined"&&(l=f);n[o]=l})}function at(n,t){return n+he(Re()*(t-n+1))}function ft(e,r,u){var i=-1,f=st(),p=e?e.length:0,s=[],v=!r&&p>=b&&f===n,h=u||v?a():s;
for(v&&(h=o(h),f=t);++i<p;){var g=e[i],y=u?u(g,i,e):g;(r?!i||h[h.length-1]!==y:0>f(h,y))&&((u||v)&&h.push(y),s.push(g))}return v?(l(h.k),c(h)):u&&l(h),s}function lt(n){return function(t,e,r){var u={};e=J.createCallback(e,r,3),r=-1;var o=t?t.length:0;if(typeof o=="number")for(;++r<o;){var i=t[r];n(u,i,e(i,r,t),t)}else h(t,function(t,r,o){n(u,t,e(t,r,o),o)});return u}}function ct(n,t,e,r,u,o){var i=1&t,a=4&t,f=16&t,l=32&t;if(!(2&t||dt(n)))throw new ie;f&&!e.length&&(t&=-17,f=e=false),l&&!r.length&&(t&=-33,l=r=false);
var c=n&&n.__bindData__;return c&&true!==c?(c=p(c),c[2]&&(c[2]=p(c[2])),c[3]&&(c[3]=p(c[3])),!i||1&c[1]||(c[4]=u),!i&&1&c[1]&&(t|=8),!a||4&c[1]||(c[5]=o),f&&be.apply(c[2]||(c[2]=[]),e),l&&we.apply(c[3]||(c[3]=[]),r),c[1]|=t,ct.apply(null,c)):(1==t||17===t?X:et)([n,t,e,r,u,o])}function pt(n){return Be[n]}function st(){var t=(t=J.indexOf)===Wt?n:t;return t}function vt(n){return typeof n=="function"&&pe.test(n)}function ht(n){var t,e;return n&&ce.call(n)==q&&(t=n.constructor,!dt(t)||t instanceof t)?(g(n,function(n,t){e=t
}),typeof e=="undefined"||me.call(n,e)):false}function gt(n){return We[n]}function yt(n){return n&&typeof n=="object"&&typeof n.length=="number"&&ce.call(n)==D||false}function mt(n,t,e){var r=Fe(n),u=r.length;for(t=tt(t,e,3);u--&&(e=r[u],false!==t(n[e],e,n)););return n}function bt(n){var t=[];return g(n,function(n,e){dt(n)&&t.push(e)}),t.sort()}function _t(n){for(var t=-1,e=Fe(n),r=e.length,u={};++t<r;){var o=e[t];u[n[o]]=o}return u}function dt(n){return typeof n=="function"}function wt(n){return!(!n||!V[typeof n])
}function jt(n){return typeof n=="number"||n&&typeof n=="object"&&ce.call(n)==W||false}function kt(n){return typeof n=="string"||n&&typeof n=="object"&&ce.call(n)==P||false}function xt(n){for(var t=-1,e=Fe(n),r=e.length,u=Xt(r);++t<r;)u[t]=n[e[t]];return u}function Ct(n,t,e){var r=-1,u=st(),o=n?n.length:0,i=false;return e=(0>e?Ie(0,o+e):e)||0,Te(n)?i=-1<u(n,t,e):typeof o=="number"?i=-1<(kt(n)?n.indexOf(t,e):u(n,t,e)):h(n,function(n){return++r<e?void 0:!(i=n===t)}),i}function Ot(n,t,e){var r=true;t=J.createCallback(t,e,3),e=-1;
var u=n?n.length:0;if(typeof u=="number")for(;++e<u&&(r=!!t(n[e],e,n)););else h(n,function(n,e,u){return r=!!t(n,e,u)});return r}function Nt(n,t,e){var r=[];t=J.createCallback(t,e,3),e=-1;var u=n?n.length:0;if(typeof u=="number")for(;++e<u;){var o=n[e];t(o,e,n)&&r.push(o)}else h(n,function(n,e,u){t(n,e,u)&&r.push(n)});return r}function It(n,t,e){t=J.createCallback(t,e,3),e=-1;var r=n?n.length:0;if(typeof r!="number"){var u;return h(n,function(n,e,r){return t(n,e,r)?(u=n,false):void 0}),u}for(;++e<r;){var o=n[e];
if(t(o,e,n))return o}}function St(n,t,e){var r=-1,u=n?n.length:0;if(t=t&&typeof e=="undefined"?t:tt(t,e,3),typeof u=="number")for(;++r<u&&false!==t(n[r],r,n););else h(n,t);return n}function Et(n,t,e){var r=n?n.length:0;if(t=t&&typeof e=="undefined"?t:tt(t,e,3),typeof r=="number")for(;r--&&false!==t(n[r],r,n););else{var u=Fe(n),r=u.length;h(n,function(n,e,o){return e=u?u[--r]:--r,t(o[e],e,o)})}return n}function Rt(n,t,e){var r=-1,u=n?n.length:0;if(t=J.createCallback(t,e,3),typeof u=="number")for(var o=Xt(u);++r<u;)o[r]=t(n[r],r,n);
else o=[],h(n,function(n,e,u){o[++r]=t(n,e,u)});return o}function At(n,t,e){var u=-1/0,o=u;if(typeof t!="function"&&e&&e[t]===n&&(t=null),null==t&&Te(n)){e=-1;for(var i=n.length;++e<i;){var a=n[e];a>o&&(o=a)}}else t=null==t&&kt(n)?r:J.createCallback(t,e,3),St(n,function(n,e,r){e=t(n,e,r),e>u&&(u=e,o=n)});return o}function Dt(n,t,e,r){if(!n)return e;var u=3>arguments.length;t=J.createCallback(t,r,4);var o=-1,i=n.length;if(typeof i=="number")for(u&&(e=n[++o]);++o<i;)e=t(e,n[o],o,n);else h(n,function(n,r,o){e=u?(u=false,n):t(e,n,r,o)
});return e}function $t(n,t,e,r){var u=3>arguments.length;return t=J.createCallback(t,r,4),Et(n,function(n,r,o){e=u?(u=false,n):t(e,n,r,o)}),e}function Tt(n){var t=-1,e=n?n.length:0,r=Xt(typeof e=="number"?e:0);return St(n,function(n){var e=at(0,++t);r[t]=r[e],r[e]=n}),r}function Ft(n,t,e){var r;t=J.createCallback(t,e,3),e=-1;var u=n?n.length:0;if(typeof u=="number")for(;++e<u&&!(r=t(n[e],e,n)););else h(n,function(n,e,u){return!(r=t(n,e,u))});return!!r}function Bt(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=-1;
for(t=J.createCallback(t,e,3);++o<u&&t(n[o],o,n);)r++}else if(r=t,null==r||e)return n?n[0]:v;return p(n,0,Se(Ie(0,r),u))}function Wt(t,e,r){if(typeof r=="number"){var u=t?t.length:0;r=0>r?Ie(0,u+r):r||0}else if(r)return r=zt(t,e),t[r]===e?r:-1;return n(t,e,r)}function qt(n,t,e){if(typeof t!="number"&&null!=t){var r=0,u=-1,o=n?n.length:0;for(t=J.createCallback(t,e,3);++u<o&&t(n[u],u,n);)r++}else r=null==t||e?1:Ie(0,t);return p(n,r)}function zt(n,t,e,r){var u=0,o=n?n.length:u;for(e=e?J.createCallback(e,r,1):Ut,t=e(t);u<o;)r=u+o>>>1,e(n[r])<t?u=r+1:o=r;
return u}function Pt(n,t,e,r){return typeof t!="boolean"&&null!=t&&(r=e,e=typeof t!="function"&&r&&r[t]===n?null:t,t=false),null!=e&&(e=J.createCallback(e,r,3)),ft(n,t,e)}function Kt(){for(var n=1<arguments.length?arguments:arguments[0],t=-1,e=n?At(Ve(n,"length")):0,r=Xt(0>e?0:e);++t<e;)r[t]=Ve(n,t);return r}function Lt(n,t){var e=-1,r=n?n.length:0,u={};for(t||!r||Te(n[0])||(t=[]);++e<r;){var o=n[e];t?u[o]=t[e]:o&&(u[o[0]]=o[1])}return u}function Mt(n,t){return 2<arguments.length?ct(n,17,p(arguments,2),null,t):ct(n,1,null,null,t)
}function Vt(n,t,e){function r(){c&&ve(c),i=c=p=v,(g||h!==t)&&(s=Ue(),a=n.apply(l,o),c||i||(o=l=null))}function u(){var e=t-(Ue()-f);0<e?c=_e(u,e):(i&&ve(i),e=p,i=c=p=v,e&&(s=Ue(),a=n.apply(l,o),c||i||(o=l=null)))}var o,i,a,f,l,c,p,s=0,h=false,g=true;if(!dt(n))throw new ie;if(t=Ie(0,t)||0,true===e)var y=true,g=false;else wt(e)&&(y=e.leading,h="maxWait"in e&&(Ie(t,e.maxWait)||0),g="trailing"in e?e.trailing:g);return function(){if(o=arguments,f=Ue(),l=this,p=g&&(c||!y),false===h)var e=y&&!c;else{i||y||(s=f);var v=h-(f-s),m=0>=v;
m?(i&&(i=ve(i)),s=f,a=n.apply(l,o)):i||(i=_e(r,v))}return m&&c?c=ve(c):c||t===h||(c=_e(u,t)),e&&(m=true,a=n.apply(l,o)),!m||c||i||(o=l=null),a}}function Ut(n){return n}function Gt(n,t,e){var r=true,u=t&&bt(t);t&&(e||u.length)||(null==e&&(e=t),o=Q,t=n,n=J,u=bt(t)),false===e?r=false:wt(e)&&"chain"in e&&(r=e.chain);var o=n,i=dt(o);St(u,function(e){var u=n[e]=t[e];i&&(o.prototype[e]=function(){var t=this.__chain__,e=this.__wrapped__,i=[e];if(be.apply(i,arguments),i=u.apply(n,i),r||t){if(e===i&&wt(i))return this;
i=new o(i),i.__chain__=t}return i})})}function Ht(){}function Jt(n){return function(t){return t[n]}}function Qt(){return this.__wrapped__}e=e?Y.defaults(G.Object(),e,Y.pick(G,A)):G;var Xt=e.Array,Yt=e.Boolean,Zt=e.Date,ne=e.Function,te=e.Math,ee=e.Number,re=e.Object,ue=e.RegExp,oe=e.String,ie=e.TypeError,ae=[],fe=re.prototype,le=e._,ce=fe.toString,pe=ue("^"+oe(ce).replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/toString| for [^\]]+/g,".*?")+"$"),se=te.ceil,ve=e.clearTimeout,he=te.floor,ge=ne.prototype.toString,ye=vt(ye=re.getPrototypeOf)&&ye,me=fe.hasOwnProperty,be=ae.push,_e=e.setTimeout,de=ae.splice,we=ae.unshift,je=function(){try{var n={},t=vt(t=re.defineProperty)&&t,e=t(n,n,n)&&t
}catch(r){}return e}(),ke=vt(ke=re.create)&&ke,xe=vt(xe=Xt.isArray)&&xe,Ce=e.isFinite,Oe=e.isNaN,Ne=vt(Ne=re.keys)&&Ne,Ie=te.max,Se=te.min,Ee=e.parseInt,Re=te.random,Ae={};Ae[$]=Xt,Ae[T]=Yt,Ae[F]=Zt,Ae[B]=ne,Ae[q]=re,Ae[W]=ee,Ae[z]=ue,Ae[P]=oe,Q.prototype=J.prototype;var De=J.support={};De.funcDecomp=!vt(e.a)&&E.test(s),De.funcNames=typeof ne.name=="string",J.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:N,variable:"",imports:{_:J}},ke||(nt=function(){function n(){}return function(t){if(wt(t)){n.prototype=t;
var r=new n;n.prototype=null}return r||e.Object()}}());var $e=je?function(n,t){M.value=t,je(n,"__bindData__",M)}:Ht,Te=xe||function(n){return n&&typeof n=="object"&&typeof n.length=="number"&&ce.call(n)==$||false},Fe=Ne?function(n){return wt(n)?Ne(n):[]}:H,Be={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},We=_t(Be),qe=ue("("+Fe(We).join("|")+")","g"),ze=ue("["+Fe(Be).join("")+"]","g"),Pe=ye?function(n){if(!n||ce.call(n)!=q)return false;var t=n.valueOf,e=vt(t)&&(e=ye(t))&&ye(e);return e?n==e||ye(n)==e:ht(n)
}:ht,Ke=lt(function(n,t,e){me.call(n,e)?n[e]++:n[e]=1}),Le=lt(function(n,t,e){(me.call(n,e)?n[e]:n[e]=[]).push(t)}),Me=lt(function(n,t,e){n[e]=t}),Ve=Rt,Ue=vt(Ue=Zt.now)&&Ue||function(){return(new Zt).getTime()},Ge=8==Ee(d+"08")?Ee:function(n,t){return Ee(kt(n)?n.replace(I,""):n,t||0)};return J.after=function(n,t){if(!dt(t))throw new ie;return function(){return 1>--n?t.apply(this,arguments):void 0}},J.assign=U,J.at=function(n){for(var t=arguments,e=-1,r=ut(t,true,false,1),t=t[2]&&t[2][t[1]]===n?1:r.length,u=Xt(t);++e<t;)u[e]=n[r[e]];
return u},J.bind=Mt,J.bindAll=function(n){for(var t=1<arguments.length?ut(arguments,true,false,1):bt(n),e=-1,r=t.length;++e<r;){var u=t[e];n[u]=ct(n[u],1,null,null,n)}return n},J.bindKey=function(n,t){return 2<arguments.length?ct(t,19,p(arguments,2),null,n):ct(t,3,null,null,n)},J.chain=function(n){return n=new Q(n),n.__chain__=true,n},J.compact=function(n){for(var t=-1,e=n?n.length:0,r=[];++t<e;){var u=n[t];u&&r.push(u)}return r},J.compose=function(){for(var n=arguments,t=n.length;t--;)if(!dt(n[t]))throw new ie;
return function(){for(var t=arguments,e=n.length;e--;)t=[n[e].apply(this,t)];return t[0]}},J.constant=function(n){return function(){return n}},J.countBy=Ke,J.create=function(n,t){var e=nt(n);return t?U(e,t):e},J.createCallback=function(n,t,e){var r=typeof n;if(null==n||"function"==r)return tt(n,t,e);if("object"!=r)return Jt(n);var u=Fe(n),o=u[0],i=n[o];return 1!=u.length||i!==i||wt(i)?function(t){for(var e=u.length,r=false;e--&&(r=ot(t[u[e]],n[u[e]],null,true)););return r}:function(n){return n=n[o],i===n&&(0!==i||1/i==1/n)
}},J.curry=function(n,t){return t=typeof t=="number"?t:+t||n.length,ct(n,4,null,null,null,t)},J.debounce=Vt,J.defaults=_,J.defer=function(n){if(!dt(n))throw new ie;var t=p(arguments,1);return _e(function(){n.apply(v,t)},1)},J.delay=function(n,t){if(!dt(n))throw new ie;var e=p(arguments,2);return _e(function(){n.apply(v,e)},t)},J.difference=function(n){return rt(n,ut(arguments,true,true,1))},J.filter=Nt,J.flatten=function(n,t,e,r){return typeof t!="boolean"&&null!=t&&(r=e,e=typeof t!="function"&&r&&r[t]===n?null:t,t=false),null!=e&&(n=Rt(n,e,r)),ut(n,t)
},J.forEach=St,J.forEachRight=Et,J.forIn=g,J.forInRight=function(n,t,e){var r=[];g(n,function(n,t){r.push(t,n)});var u=r.length;for(t=tt(t,e,3);u--&&false!==t(r[u--],r[u],n););return n},J.forOwn=h,J.forOwnRight=mt,J.functions=bt,J.groupBy=Le,J.indexBy=Me,J.initial=function(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=u;for(t=J.createCallback(t,e,3);o--&&t(n[o],o,n);)r++}else r=null==t||e?1:t||r;return p(n,0,Se(Ie(0,u-r),u))},J.intersection=function(){for(var e=[],r=-1,u=arguments.length,i=a(),f=st(),p=f===n,s=a();++r<u;){var v=arguments[r];
(Te(v)||yt(v))&&(e.push(v),i.push(p&&v.length>=b&&o(r?e[r]:s)))}var p=e[0],h=-1,g=p?p.length:0,y=[];n:for(;++h<g;){var m=i[0],v=p[h];if(0>(m?t(m,v):f(s,v))){for(r=u,(m||s).push(v);--r;)if(m=i[r],0>(m?t(m,v):f(e[r],v)))continue n;y.push(v)}}for(;u--;)(m=i[u])&&c(m);return l(i),l(s),y},J.invert=_t,J.invoke=function(n,t){var e=p(arguments,2),r=-1,u=typeof t=="function",o=n?n.length:0,i=Xt(typeof o=="number"?o:0);return St(n,function(n){i[++r]=(u?t:n[t]).apply(n,e)}),i},J.keys=Fe,J.map=Rt,J.mapValues=function(n,t,e){var r={};
return t=J.createCallback(t,e,3),h(n,function(n,e,u){r[e]=t(n,e,u)}),r},J.max=At,J.memoize=function(n,t){function e(){var r=e.cache,u=t?t.apply(this,arguments):m+arguments[0];return me.call(r,u)?r[u]:r[u]=n.apply(this,arguments)}if(!dt(n))throw new ie;return e.cache={},e},J.merge=function(n){var t=arguments,e=2;if(!wt(n))return n;if("number"!=typeof t[2]&&(e=t.length),3<e&&"function"==typeof t[e-2])var r=tt(t[--e-1],t[e--],2);else 2<e&&"function"==typeof t[e-1]&&(r=t[--e]);for(var t=p(arguments,1,e),u=-1,o=a(),i=a();++u<e;)it(n,t[u],r,o,i);
return l(o),l(i),n},J.min=function(n,t,e){var u=1/0,o=u;if(typeof t!="function"&&e&&e[t]===n&&(t=null),null==t&&Te(n)){e=-1;for(var i=n.length;++e<i;){var a=n[e];a<o&&(o=a)}}else t=null==t&&kt(n)?r:J.createCallback(t,e,3),St(n,function(n,e,r){e=t(n,e,r),e<u&&(u=e,o=n)});return o},J.omit=function(n,t,e){var r={};if(typeof t!="function"){var u=[];g(n,function(n,t){u.push(t)});for(var u=rt(u,ut(arguments,true,false,1)),o=-1,i=u.length;++o<i;){var a=u[o];r[a]=n[a]}}else t=J.createCallback(t,e,3),g(n,function(n,e,u){t(n,e,u)||(r[e]=n)
});return r},J.once=function(n){var t,e;if(!dt(n))throw new ie;return function(){return t?e:(t=true,e=n.apply(this,arguments),n=null,e)}},J.pairs=function(n){for(var t=-1,e=Fe(n),r=e.length,u=Xt(r);++t<r;){var o=e[t];u[t]=[o,n[o]]}return u},J.partial=function(n){return ct(n,16,p(arguments,1))},J.partialRight=function(n){return ct(n,32,null,p(arguments,1))},J.pick=function(n,t,e){var r={};if(typeof t!="function")for(var u=-1,o=ut(arguments,true,false,1),i=wt(n)?o.length:0;++u<i;){var a=o[u];a in n&&(r[a]=n[a])
}else t=J.createCallback(t,e,3),g(n,function(n,e,u){t(n,e,u)&&(r[e]=n)});return r},J.pluck=Ve,J.property=Jt,J.pull=function(n){for(var t=arguments,e=0,r=t.length,u=n?n.length:0;++e<r;)for(var o=-1,i=t[e];++o<u;)n[o]===i&&(de.call(n,o--,1),u--);return n},J.range=function(n,t,e){n=+n||0,e=typeof e=="number"?e:+e||1,null==t&&(t=n,n=0);var r=-1;t=Ie(0,se((t-n)/(e||1)));for(var u=Xt(t);++r<t;)u[r]=n,n+=e;return u},J.reject=function(n,t,e){return t=J.createCallback(t,e,3),Nt(n,function(n,e,r){return!t(n,e,r)
})},J.remove=function(n,t,e){var r=-1,u=n?n.length:0,o=[];for(t=J.createCallback(t,e,3);++r<u;)e=n[r],t(e,r,n)&&(o.push(e),de.call(n,r--,1),u--);return o},J.rest=qt,J.shuffle=Tt,J.sortBy=function(n,t,e){var r=-1,o=Te(t),i=n?n.length:0,p=Xt(typeof i=="number"?i:0);for(o||(t=J.createCallback(t,e,3)),St(n,function(n,e,u){var i=p[++r]=f();o?i.m=Rt(t,function(t){return n[t]}):(i.m=a())[0]=t(n,e,u),i.n=r,i.o=n}),i=p.length,p.sort(u);i--;)n=p[i],p[i]=n.o,o||l(n.m),c(n);return p},J.tap=function(n,t){return t(n),n
},J.throttle=function(n,t,e){var r=true,u=true;if(!dt(n))throw new ie;return false===e?r=false:wt(e)&&(r="leading"in e?e.leading:r,u="trailing"in e?e.trailing:u),L.leading=r,L.maxWait=t,L.trailing=u,Vt(n,t,L)},J.times=function(n,t,e){n=-1<(n=+n)?n:0;var r=-1,u=Xt(n);for(t=tt(t,e,1);++r<n;)u[r]=t(r);return u},J.toArray=function(n){return n&&typeof n.length=="number"?p(n):xt(n)},J.transform=function(n,t,e,r){var u=Te(n);if(null==e)if(u)e=[];else{var o=n&&n.constructor;e=nt(o&&o.prototype)}return t&&(t=J.createCallback(t,r,4),(u?St:h)(n,function(n,r,u){return t(e,n,r,u)
})),e},J.union=function(){return ft(ut(arguments,true,true))},J.uniq=Pt,J.values=xt,J.where=Nt,J.without=function(n){return rt(n,p(arguments,1))},J.wrap=function(n,t){return ct(t,16,[n])},J.xor=function(){for(var n=-1,t=arguments.length;++n<t;){var e=arguments[n];if(Te(e)||yt(e))var r=r?ft(rt(r,e).concat(rt(e,r))):e}return r||[]},J.zip=Kt,J.zipObject=Lt,J.collect=Rt,J.drop=qt,J.each=St,J.eachRight=Et,J.extend=U,J.methods=bt,J.object=Lt,J.select=Nt,J.tail=qt,J.unique=Pt,J.unzip=Kt,Gt(J),J.clone=function(n,t,e,r){return typeof t!="boolean"&&null!=t&&(r=e,e=t,t=false),Z(n,t,typeof e=="function"&&tt(e,r,1))
},J.cloneDeep=function(n,t,e){return Z(n,true,typeof t=="function"&&tt(t,e,1))},J.contains=Ct,J.escape=function(n){return null==n?"":oe(n).replace(ze,pt)},J.every=Ot,J.find=It,J.findIndex=function(n,t,e){var r=-1,u=n?n.length:0;for(t=J.createCallback(t,e,3);++r<u;)if(t(n[r],r,n))return r;return-1},J.findKey=function(n,t,e){var r;return t=J.createCallback(t,e,3),h(n,function(n,e,u){return t(n,e,u)?(r=e,false):void 0}),r},J.findLast=function(n,t,e){var r;return t=J.createCallback(t,e,3),Et(n,function(n,e,u){return t(n,e,u)?(r=n,false):void 0
}),r},J.findLastIndex=function(n,t,e){var r=n?n.length:0;for(t=J.createCallback(t,e,3);r--;)if(t(n[r],r,n))return r;return-1},J.findLastKey=function(n,t,e){var r;return t=J.createCallback(t,e,3),mt(n,function(n,e,u){return t(n,e,u)?(r=e,false):void 0}),r},J.has=function(n,t){return n?me.call(n,t):false},J.identity=Ut,J.indexOf=Wt,J.isArguments=yt,J.isArray=Te,J.isBoolean=function(n){return true===n||false===n||n&&typeof n=="object"&&ce.call(n)==T||false},J.isDate=function(n){return n&&typeof n=="object"&&ce.call(n)==F||false
},J.isElement=function(n){return n&&1===n.nodeType||false},J.isEmpty=function(n){var t=true;if(!n)return t;var e=ce.call(n),r=n.length;return e==$||e==P||e==D||e==q&&typeof r=="number"&&dt(n.splice)?!r:(h(n,function(){return t=false}),t)},J.isEqual=function(n,t,e,r){return ot(n,t,typeof e=="function"&&tt(e,r,2))},J.isFinite=function(n){return Ce(n)&&!Oe(parseFloat(n))},J.isFunction=dt,J.isNaN=function(n){return jt(n)&&n!=+n},J.isNull=function(n){return null===n},J.isNumber=jt,J.isObject=wt,J.isPlainObject=Pe,J.isRegExp=function(n){return n&&typeof n=="object"&&ce.call(n)==z||false
},J.isString=kt,J.isUndefined=function(n){return typeof n=="undefined"},J.lastIndexOf=function(n,t,e){var r=n?n.length:0;for(typeof e=="number"&&(r=(0>e?Ie(0,r+e):Se(e,r-1))+1);r--;)if(n[r]===t)return r;return-1},J.mixin=Gt,J.noConflict=function(){return e._=le,this},J.noop=Ht,J.now=Ue,J.parseInt=Ge,J.random=function(n,t,e){var r=null==n,u=null==t;return null==e&&(typeof n=="boolean"&&u?(e=n,n=1):u||typeof t!="boolean"||(e=t,u=true)),r&&u&&(t=1),n=+n||0,u?(t=n,n=0):t=+t||0,e||n%1||t%1?(e=Re(),Se(n+e*(t-n+parseFloat("1e-"+((e+"").length-1))),t)):at(n,t)
},J.reduce=Dt,J.reduceRight=$t,J.result=function(n,t){if(n){var e=n[t];return dt(e)?n[t]():e}},J.runInContext=s,J.size=function(n){var t=n?n.length:0;return typeof t=="number"?t:Fe(n).length},J.some=Ft,J.sortedIndex=zt,J.template=function(n,t,e){var r=J.templateSettings;n=oe(n||""),e=_({},e,r);var u,o=_({},e.imports,r.imports),r=Fe(o),o=xt(o),a=0,f=e.interpolate||S,l="__p+='",f=ue((e.escape||S).source+"|"+f.source+"|"+(f===N?x:S).source+"|"+(e.evaluate||S).source+"|$","g");n.replace(f,function(t,e,r,o,f,c){return r||(r=o),l+=n.slice(a,c).replace(R,i),e&&(l+="'+__e("+e+")+'"),f&&(u=true,l+="';"+f+";\n__p+='"),r&&(l+="'+((__t=("+r+"))==null?'':__t)+'"),a=c+t.length,t
}),l+="';",f=e=e.variable,f||(e="obj",l="with("+e+"){"+l+"}"),l=(u?l.replace(w,""):l).replace(j,"$1").replace(k,"$1;"),l="function("+e+"){"+(f?"":e+"||("+e+"={});")+"var __t,__p='',__e=_.escape"+(u?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+l+"return __p}";try{var c=ne(r,"return "+l).apply(v,o)}catch(p){throw p.source=l,p}return t?c(t):(c.source=l,c)},J.unescape=function(n){return null==n?"":oe(n).replace(qe,gt)},J.uniqueId=function(n){var t=++y;return oe(null==n?"":n)+t
},J.all=Ot,J.any=Ft,J.detect=It,J.findWhere=It,J.foldl=Dt,J.foldr=$t,J.include=Ct,J.inject=Dt,Gt(function(){var n={};return h(J,function(t,e){J.prototype[e]||(n[e]=t)}),n}(),false),J.first=Bt,J.last=function(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=u;for(t=J.createCallback(t,e,3);o--&&t(n[o],o,n);)r++}else if(r=t,null==r||e)return n?n[u-1]:v;return p(n,Ie(0,u-r))},J.sample=function(n,t,e){return n&&typeof n.length!="number"&&(n=xt(n)),null==t||e?n?n[at(0,n.length-1)]:v:(n=Tt(n),n.length=Se(Ie(0,t),n.length),n)
},J.take=Bt,J.head=Bt,h(J,function(n,t){var e="sample"!==t;J.prototype[t]||(J.prototype[t]=function(t,r){var u=this.__chain__,o=n(this.__wrapped__,t,r);return u||null!=t&&(!r||e&&typeof t=="function")?new Q(o,u):o})}),J.VERSION="2.4.1",J.prototype.chain=function(){return this.__chain__=true,this},J.prototype.toString=function(){return oe(this.__wrapped__)},J.prototype.value=Qt,J.prototype.valueOf=Qt,St(["join","pop","shift"],function(n){var t=ae[n];J.prototype[n]=function(){var n=this.__chain__,e=t.apply(this.__wrapped__,arguments);
return n?new Q(e,n):e}}),St(["push","reverse","sort","unshift"],function(n){var t=ae[n];J.prototype[n]=function(){return t.apply(this.__wrapped__,arguments),this}}),St(["concat","slice","splice"],function(n){var t=ae[n];J.prototype[n]=function(){return new Q(t.apply(this.__wrapped__,arguments),this.__chain__)}}),J}var v,h=[],g=[],y=0,m=+new Date+"",b=75,_=40,d=" \t\x0B\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000",w=/\b__p\+='';/g,j=/\b(__p\+=)''\+/g,k=/(__e\(.*?\)|\b__t\))\+'';/g,x=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,C=/\w*$/,O=/^\s*function[ \n\r\t]+\w/,N=/<%=([\s\S]+?)%>/g,I=RegExp("^["+d+"]*0+(?=.$)"),S=/($^)/,E=/\bthis\b/,R=/['\n\r\t\u2028\u2029\\]/g,A="Array Boolean Date Function Math Number Object RegExp String _ attachEvent clearTimeout isFinite isNaN parseInt setTimeout".split(" "),D="[object Arguments]",$="[object Array]",T="[object Boolean]",F="[object Date]",B="[object Function]",W="[object Number]",q="[object Object]",z="[object RegExp]",P="[object String]",K={};
K[B]=false,K[D]=K[$]=K[T]=K[F]=K[W]=K[q]=K[z]=K[P]=true;var L={leading:false,maxWait:0,trailing:false},M={configurable:false,enumerable:false,value:null,writable:false},V={"boolean":false,"function":true,object:true,number:false,string:false,undefined:false},U={"\\":"\\","'":"'","\n":"n","\r":"r","\t":"t","\u2028":"u2028","\u2029":"u2029"},G=V[typeof window]&&window||this,H=V[typeof exports]&&exports&&!exports.nodeType&&exports,J=V[typeof module]&&module&&!module.nodeType&&module,Q=J&&J.exports===H&&H,X=V[typeof global]&&global;!X||X.global!==X&&X.window!==X||(G=X);
var Y=s();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(G._=Y, define(function(){return Y})):H&&J?Q?(J.exports=Y)._=Y:H._=Y:G._=Y}).call(this);

View File

@ -2,7 +2,7 @@
<h2>{{ error }}</h2>
</div>
<div ng-hide="error">
<div class="row" ng-hide="requests">
<div class="row" ng-show="(bin.requestCount == 0)">
<div class="col-lg-8 col-lg-offset-2">
<h2>Bin url:</h2>
<div>

View File

@ -1,5 +1,5 @@
<div class="row">
<div class="col-lg-offset-3 col-lg-5">
<div class="col-lg-offset-3 col-lg-5 col-md-offset-1 col-md-7 col-sm-8 col-xs-12">
<div class="row">
<p>RequestBin gives you a URL that will collect requests made to it and let you inspect them in a human-friendly way.
Use RequestBin to see what your HTTP client is sending or to inspect and debug webhook requests.</p>
@ -14,7 +14,7 @@
</div>
</div>
<div class="col-lg-offset-1 col-lg-3">
<div class="col-lg-offset-1 col-lg-3 col-md-offset-1 col-md-3 col-sm-offset-1 col-sm-3 col-xs-12">
<div>
<ul class="nav nav-pills nav-stacked">
<li><h4>History</h4></li>

View File

@ -38,11 +38,11 @@
</div>
<script src="/static/lib/angular.js"></script>
<script src="/static/lib/angular-route.js"></script>
<script src="/static/lib/lodash.js"></script>
<script src="/static/lib/angular.min.js"></script>
<script src="/static/lib/angular-route.min.js"></script>
<script src="/static/lib/lodash.min.js"></script>
<script src="/static/lib/angular-ui-router.min.js"></script>
<script src="/static/lib/restangular.js"></script>
<script src="/static/lib/restangular.min.js"></script>

22
src/go.mod Normal file
View File

@ -0,0 +1,22 @@
module git.tiburon.su/USEFULL/skimmer
go 1.18
require (
github.com/codegangsta/martini v0.0.0-20170121215854-22fa46961aab
github.com/codegangsta/martini-contrib v0.0.0-20140208234550-8ce6181c2609
github.com/garyburd/redigo v1.6.3
github.com/stretchr/testify v1.8.0
github.com/ugorji/go/codec v1.2.7
)
require (
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/gorilla/sessions v1.2.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

33
src/go.sum Normal file
View File

@ -0,0 +1,33 @@
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q=
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
github.com/codegangsta/martini v0.0.0-20170121215854-22fa46961aab h1:eFEFkK1s2OXNiIOvjNT5lk/AGXQodN2l8VgbkCK9r4c=
github.com/codegangsta/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:0SkifPRh0YknjZR6LxtP+eMvgPwUI6DG8hE9U/3dW9E=
github.com/codegangsta/martini-contrib v0.0.0-20140208234550-8ce6181c2609 h1:aRxx5sQikIjKQPDVpYbEjaUSrj58MM5kfxX2wS9nNNQ=
github.com/codegangsta/martini-contrib v0.0.0-20140208234550-8ce6181c2609/go.mod h1:Hr/9ecwnTZD7izDjg7HoB5wOJ01ZbNo+ntSTza2hqR4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/garyburd/redigo v1.6.3 h1:HCeeRluvAgMusMomi1+6Y5dmFOdYV/JzoRrrbFlkGIc=
github.com/garyburd/redigo v1.6.3/go.mod h1:rTb6epsqigu3kYKBnaF028A7Tf/Aw5s0cqA47doKKqw=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,12 +1,34 @@
package main
import (
"skimmer"
"flag"
"fmt"
"git.tiburon.su/USEFULL/skimmer/skimmer"
)
func main() {
api := skimmer.GetApi(&skimmer.Config{
var (
config = skimmer.Config{
Address: "localhost:3000",
SessionSecret: "secret123",
})
api.Run()
RedisConfig: skimmer.RedisConfig{
RedisAddr: "127.0.0.1:6379",
RedisPassword: "",
RedisPrefix: "skimmer",
},
}
)
func init() {
flag.StringVar(&config.Storage, "storage", "memory", "available storages: redis, memory")
flag.StringVar(&config.SessionSecret, "sessionSecret", config.SessionSecret, "")
flag.StringVar(&config.RedisAddr, "redisAddr", config.RedisAddr, "redis storage only")
flag.StringVar(&config.RedisPassword, "redisPassword", config.RedisPassword, "redis storage only")
flag.StringVar(&config.RedisPrefix, "redisPrefix", config.RedisPrefix, "redis storage only")
}
func main() {
flag.Parse()
api := skimmer.GetApi(&config)
fmt.Printf("Open in brouser http://%s\n", config.Address)
api.RunOnAddr(config.Address)
}

View File

@ -1,137 +1,154 @@
package skimmer
import (
"fmt"
"github.com/codegangsta/martini"
"github.com/codegangsta/martini-contrib/render"
"github.com/codegangsta/martini-contrib/sessions"
"net/http"
"strconv"
"github.com/codegangsta/martini-contrib/sessions"
"fmt"
)
type ErrorMsg struct{
type ErrorMsg struct {
Error string `json:"error"`
}
const (
REQUEST_BODY_SIZE = 1024 * 30
MAX_REQUEST_COUNT = 20
BIN_LIFETIME = 60 * 60 * 24 * 2
BIN_LIFETIME = 60 * 60 * 24 * 2
)
type RedisConfig struct {
RedisAddr string
RedisPassword string
RedisPrefix string
}
type Config struct {
SessionSecret string
Address string
SessionSecret string
Storage string
RedisConfig
}
func GetApi(config *Config) *martini.ClassicMartini {
storage := NewMemoryStorage(MAX_REQUEST_COUNT, BIN_LIFETIME)
var storage Storage
switch config.Storage {
case "redis":
redisStorage := NewRedisStorage(config.RedisAddr, config.RedisPassword, config.RedisPassword, MAX_REQUEST_COUNT, BIN_LIFETIME)
redisStorage.StartCleaning(60)
storage = redisStorage
default:
memoryStorage := NewMemoryStorage(MAX_REQUEST_COUNT, BIN_LIFETIME)
memoryStorage.StartCleaning(60)
storage = memoryStorage
}
store := sessions.NewCookieStore([]byte(config.SessionSecret))
api := martini.Classic()
api.MapTo(storage, (*Storage)(nil))
api.Use(render.Renderer(render.Options{
Directory: "public/static/views",
Directory: "public/static/views",
Extensions: []string{".html"},
Delims: render.Delims{"{[{", "}]}"},
Delims: render.Delims{"{[{", "}]}"},
}))
api.Use(sessions.Sessions("my_session", store))
api.Use(NewSessionHistoryHandler(20, "binHistory"))
api.Post("/api/v1/bins/", func(r render.Render, storage Storage, history History, session sessions.Session, req *http.Request) {
payload := Bin{}
if err := DecodeJsonPayload(req, &payload); err != nil {
r.JSON(400, ErrorMsg{fmt.Sprintf("Decoding payload error: %s", err)})
return
}
bin := NewBin()
if payload.Private {
bin.SetPrivate()
}
if err := storage.CreateBin(bin); err == nil {
history.Add(bin.Name)
if bin.Private {
session.Set(fmt.Sprintf("pr_%s", bin.Name), bin.SecretKey)
}
r.JSON(http.StatusCreated, bin)
} else {
r.JSON(http.StatusInternalServerError, ErrorMsg{err.Error()})
}
})
api.Post("/api/v1/bins/", func(r render.Render, storage Storage, history History, session sessions.Session, req *http.Request){
payload := Bin{}
if err := DecodeJsonPayload(req, &payload); err != nil {
r.JSON(400, ErrorMsg{fmt.Sprintf("Decoding payload error: %s", err)})
return
}
bin := NewBin()
if payload.Private {
bin.SetPrivate()
}
if err := storage.CreateBin(bin); err == nil {
history.Add(bin.Name)
if bin.Private {
session.Set(fmt.Sprintf("pr_%s", bin.Name), bin.SecretKey)
}
r.JSON(http.StatusCreated, bin)
} else {
r.JSON(http.StatusInternalServerError, ErrorMsg{err.Error()})
}
})
api.Get("/api/v1/bins/", func(r render.Render, storage Storage, history History) {
if bins, err := storage.LookupBins(history.All()); err == nil {
r.JSON(http.StatusOK, bins)
} else {
r.JSON(http.StatusInternalServerError, ErrorMsg{err.Error()})
}
})
api.Get("/api/v1/bins/", func(r render.Render, storage Storage, history History){
if bins, err := storage.LookupBins(history.All()); err == nil {
r.JSON(http.StatusOK, bins)
api.Get("/api/v1/bins/:bin", func(r render.Render, params martini.Params, session sessions.Session, storage Storage) {
if bin, err := storage.LookupBin(params["bin"]); err == nil {
if bin.Private && bin.SecretKey != session.Get(fmt.Sprintf("pr_%s", bin.Name)) {
r.JSON(http.StatusForbidden, ErrorMsg{"The bin is private"})
} else {
r.JSON(http.StatusInternalServerError, ErrorMsg{err.Error()})
r.JSON(http.StatusOK, bin)
}
})
api.Get("/api/v1/bins/:bin", func(r render.Render, params martini.Params, session sessions.Session, storage Storage){
if bin, err := storage.LookupBin(params["bin"]); err == nil{
if bin.Private && bin.SecretKey != session.Get(fmt.Sprintf("pr_%s", bin.Name)){
r.JSON(http.StatusForbidden, ErrorMsg{"The bin is private"})
} else {
r.JSON(http.StatusOK, bin)
}
} else {
r.JSON(http.StatusNotFound, ErrorMsg{err.Error()})
}
})
} else {
r.JSON(http.StatusNotFound, ErrorMsg{err.Error()})
}
})
api.Get("/api/v1/bins/:bin/requests/", func(r render.Render, storage Storage, session sessions.Session,
params martini.Params, req *http.Request){
if bin, error := storage.LookupBin(params["bin"]); error == nil {
if bin.Private && bin.SecretKey != session.Get(fmt.Sprintf("pr_%s", bin.Name)){
r.JSON(http.StatusForbidden, ErrorMsg{"The bin is private"})
} else {
from := 0
to := 20
if fromVal, err := strconv.Atoi(req.FormValue("from")); err == nil {
from = fromVal
}
if toVal, err := strconv.Atoi(req.FormValue("to")); err == nil {
to = toVal
}
if requests, err := storage.LookupRequests(bin.Name, from, to); err == nil {
r.JSON(http.StatusOK, requests)
} else {
r.JSON(http.StatusInternalServerError, ErrorMsg{err.Error()})
}
params martini.Params, req *http.Request) {
if bin, error := storage.LookupBin(params["bin"]); error == nil {
if bin.Private && bin.SecretKey != session.Get(fmt.Sprintf("pr_%s", bin.Name)) {
r.JSON(http.StatusForbidden, ErrorMsg{"The bin is private"})
} else {
from := 0
to := 20
if fromVal, err := strconv.Atoi(req.FormValue("from")); err == nil {
from = fromVal
}
} else {
r.Error(http.StatusNotFound)
}
})
api.Get("/api/v1/bins/:bin/requests/:request", func(r render.Render, storage Storage, params martini.Params){
if request, err := storage.LookupRequest(params["bin"], params["request"]); err == nil {
r.JSON(http.StatusOK, request)
} else {
r.JSON(http.StatusNotFound, ErrorMsg{err.Error()})
}
})
api.Any("/bins/:name", func(r render.Render, storage Storage, params martini.Params,
req *http.Request){
if bin, error := storage.LookupBin(params["name"]); error == nil {
request := NewRequest(req, REQUEST_BODY_SIZE)
if err := storage.CreateRequest(bin, request); err == nil {
r.JSON(http.StatusOK, request)
if toVal, err := strconv.Atoi(req.FormValue("to")); err == nil {
to = toVal
}
if requests, err := storage.LookupRequests(bin.Name, from, to); err == nil {
r.JSON(http.StatusOK, requests)
} else {
r.JSON(http.StatusInternalServerError, ErrorMsg{err.Error()})
}
} else {
r.Error(http.StatusNotFound)
}
})
} else {
r.Error(http.StatusNotFound)
}
})
api.Get("**", func(r render.Render){
r.HTML(200, "index", nil)
})
api.Get("/api/v1/bins/:bin/requests/:request", func(r render.Render, storage Storage, params martini.Params) {
if request, err := storage.LookupRequest(params["bin"], params["request"]); err == nil {
r.JSON(http.StatusOK, request)
} else {
r.JSON(http.StatusNotFound, ErrorMsg{err.Error()})
}
})
api.Any("/bins/:name", func(r render.Render, storage Storage, params martini.Params,
req *http.Request) {
if bin, error := storage.LookupBin(params["name"]); error == nil {
request := NewRequest(req, REQUEST_BODY_SIZE)
if err := storage.CreateRequest(bin, request); err == nil {
r.JSON(http.StatusOK, request)
} else {
r.JSON(http.StatusInternalServerError, ErrorMsg{err.Error()})
}
} else {
r.Error(http.StatusNotFound)
}
})
api.Get("**", func(r render.Render) {
r.HTML(200, "index", nil)
})
return api
}

View File

@ -83,6 +83,10 @@ func NewRequest(httpRequest *http.Request, maxBodySize int) *Request {
} else {
formValue = httpRequest.PostForm
}
if realIp := httpRequest.Header.Get("X-Real-Ip"); realIp != "" {
httpRequest.Header.Del("X-Real-Ip")
httpRequest.RemoteAddr = realIp
}
request := Request{
Id: rs.Generate(12),
Created: time.Now().Unix(),

299
src/skimmer/redis.go Normal file
View File

@ -0,0 +1,299 @@
package skimmer
import (
"errors"
"fmt"
"github.com/garyburd/redigo/redis"
"github.com/ugorji/go/codec"
"strings"
"time"
)
type RedisStorage struct {
BaseStorage
pool *redis.Pool
prefix string
cleanTimer *time.Timer
}
const (
KEY_SEPARATOR = "|"
BIN_KEY = "bins"
REQUESTS_KEY = "rq"
REQUEST_HASH_KEY = "rhsh"
CLEANING_SET = "cln"
CLEANING_FACTOR = 3
)
func getPool(server string, password string) (pool *redis.Pool) {
pool = &redis.Pool{
MaxIdle: 3,
IdleTimeout: 240 * time.Second,
Dial: func() (redis.Conn, error) {
c, err := redis.Dial("tcp", server)
if err != nil {
return nil, err
}
if password != "" {
if _, err := c.Do("AUTH", password); err != nil {
c.Close()
return nil, err
}
}
return c, err
},
TestOnBorrow: func(c redis.Conn, _ time.Time) error {
_, err := c.Do("PING")
return err
},
}
return pool
}
func NewRedisStorage(server, password, prefix string, maxRequests int, binLifetime int64) *RedisStorage {
return &RedisStorage{
BaseStorage{maxRequests, binLifetime},
getPool(server, password),
prefix,
&time.Timer{},
}
}
func (storage *RedisStorage) StartCleaning(timeout int) {
defer func(){
storage.cleanTimer = time.AfterFunc(time.Duration(timeout) * time.Second, func(){storage.StartCleaning(timeout)})
}()
storage.clean()
}
func (storage *RedisStorage) StopCleaning() {
if storage.cleanTimer != nil {
storage.cleanTimer.Stop()
}
}
func (storage *RedisStorage) clean() {
for {
conn := storage.pool.Get()
defer conn.Close()
binName, err := redis.String(conn.Do("SPOP", storage.getKey(CLEANING_SET)))
if err != nil {
break
}
conn.Send("LRANGE", storage.getKey(REQUESTS_KEY, binName), storage.maxRequests, -1)
conn.Send("LTRIM", storage.getKey(REQUESTS_KEY, binName), 0, storage.maxRequests-1)
conn.Flush()
if values, error := redis.Values(conn.Receive()); error == nil {
ids := []string{}
if err := redis.ScanSlice(values, &ids); err != nil {
continue
}
if len(ids) > 0 {
args := redis.Args{}.Add(storage.getKey(REQUEST_HASH_KEY, binName)).AddFlat(ids)
conn.Do("HDEL", args...)
}
}
}
}
func (storage *RedisStorage) getKey(keys ...string) string {
return fmt.Sprintf("%s%s%s", storage.prefix, KEY_SEPARATOR, strings.Join(keys, KEY_SEPARATOR))
}
func (storage *RedisStorage) LookupBin(name string) (bin *Bin, err error) {
conn := storage.pool.Get()
defer conn.Close()
reply, err := redis.Bytes(conn.Do("GET", storage.getKey(BIN_KEY, name)))
if err != nil {
if err == redis.ErrNil {
err = errors.New("Bin was not found")
}
return
}
err = storage.Load(reply, &bin)
return
}
func (storage *RedisStorage) LookupBins(names []string) ([]*Bin, error) {
bins := []*Bin{}
if len(names) == 0 {
return bins, nil
}
args := redis.Args{}
for _, name := range names {
args = args.Add(storage.getKey(BIN_KEY, name))
}
conn := storage.pool.Get()
defer conn.Close()
if values, err := redis.Values(conn.Do("MGET", args...)); err == nil {
bytes := [][]byte{}
if err = redis.ScanSlice(values, &bytes); err != nil {
return nil, err
}
for _, rawbin := range bytes {
if len(rawbin) > 0 {
bin := &Bin{}
if err := storage.Load(rawbin, bin); err == nil {
bins = append(bins, bin)
}
}
}
return bins, nil
} else {
return nil, err
}
}
func (storage *RedisStorage) UpdateBin(bin *Bin) (err error) {
dumpedBin, err := storage.Dump(bin)
if err != nil {
return
}
conn := storage.pool.Get()
defer conn.Close()
key := storage.getKey(BIN_KEY, bin.Name)
err = conn.Send("SET", key, dumpedBin)
if err != nil {
return
}
err = conn.Send("EXPIRE", key, storage.binLifetime)
conn.Flush()
return err
}
func (storage *RedisStorage) LookupRequest(binName string, id string) (req *Request, err error) {
conn := storage.pool.Get()
defer conn.Close()
data, err := redis.Bytes(conn.Do("HGET", storage.getKey(REQUEST_HASH_KEY, binName), id))
if err != nil {
if err == redis.ErrNil {
err = errors.New("Request was not found")
}
return
}
req = &Request{}
err = storage.Load(data, req)
return
}
func (storage *RedisStorage) LookupRequests(binName string, from int, to int) ([]*Request, error) {
conn := storage.pool.Get()
requests := []*Request{}
defer conn.Close()
if from < 0 {
return nil, errors.New("from argument should be more then 0")
}
if to < 0 {
return nil, errors.New("to argument should be more then 0")
}
if to > storage.maxRequests {
to = storage.maxRequests
}
if from > to {
return nil, errors.New("value of agrument to should be less then value of argument from")
}
if from == to {
return requests, nil
}
if values, err := redis.Values(conn.Do("LRANGE", storage.getKey(REQUESTS_KEY, binName), from, to-1)); err == nil {
ids := []string{}
if error := redis.ScanSlice(values, &ids); error != nil {
return nil, error
}
if len(ids) > 0 {
args := redis.Args{}.Add(storage.getKey(REQUEST_HASH_KEY, binName)).AddFlat(ids)
if values, err := redis.Values(conn.Do("HMGET", args...)); err == nil {
bytes := [][]byte{}
if error := redis.ScanSlice(values, &bytes); error != nil {
return nil, error
}
for _, raw := range bytes {
if len(raw) > 0 {
request := &Request{}
if err := storage.Load(raw, request); err == nil {
requests = append(requests, request)
}
}
}
} else {
return nil, err
}
}
return requests, nil
} else {
return nil, err
}
}
func (storage *RedisStorage) CreateBin(bin *Bin) error {
if err := storage.UpdateBin(bin); err != nil {
return err
}
return nil
}
func (storage *RedisStorage) CreateRequest(bin *Bin, req *Request) (err error) {
data, err := storage.Dump(req)
if err != nil {
return
}
conn := storage.pool.Get()
defer conn.Close()
key := storage.getKey(REQUESTS_KEY, bin.Name)
err = conn.Send("LPUSH", key, req.Id)
if err != nil {
return
}
err = conn.Send("EXPIRE", key, storage.binLifetime)
if err != nil {
return
}
key = storage.getKey(REQUEST_HASH_KEY, bin.Name)
err = conn.Send("HSET", key, req.Id, data)
if err != nil {
return
}
err = conn.Send("EXPIRE", key, storage.binLifetime)
if err != nil {
return
}
conn.Flush()
requestCount, err := redis.Int(conn.Receive())
if err != nil {
return
}
if requestCount < storage.maxRequests {
bin.RequestCount = requestCount
} else {
bin.RequestCount = storage.maxRequests
}
bin.Updated = time.Now().Unix()
if _, err = redis.Int(conn.Receive()); err != nil {
return
}
if requestCount > storage.maxRequests * CLEANING_FACTOR {
conn.Do("SADD", storage.getKey(CLEANING_SET), bin.Name)
}
if err = storage.UpdateBin(bin); err != nil {
return
}
return
}
func (storage *RedisStorage) Dump(v interface{}) (data []byte, err error) {
var (
mh codec.MsgpackHandle
h = &mh
)
err = codec.NewEncoderBytes(&data, h).Encode(v)
return
}
func (storage *RedisStorage) Load(data []byte, v interface{}) error {
var (
mh codec.MsgpackHandle
h = &mh
)
return codec.NewDecoderBytes(data, h).Decode(v)
}

326
src/skimmer/redis_test.go Normal file
View File

@ -0,0 +1,326 @@
package skimmer
import (
"github.com/garyburd/redigo/redis"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"testing"
// "github.com/davecgh/go-spew/spew"
"bytes"
"net/http"
"errors"
"fmt"
"time"
)
type MockedRedisConn struct {
mock.Mock
}
func (c *MockedRedisConn) Close() error {
args := c.Mock.Called()
return args.Error(0)
}
func (c *MockedRedisConn) Err() error {
args := c.Mock.Called()
return args.Error(0)
}
func (c *MockedRedisConn) Do(commandName string, params ...interface{}) (interface{}, error) {
// spew.Dump(commandName, params)
args := c.Mock.Called(commandName, params)
return args.Get(0), args.Error(1)
}
func (c *MockedRedisConn) Send(commandName string, params ...interface{}) error {
// spew.Dump(commandName, params)
args := c.Mock.Called(commandName, params)
return args.Error(0)
}
func (c *MockedRedisConn) Flush() error {
args := c.Mock.Called()
return args.Error(0)
}
func (c *MockedRedisConn) Receive() (interface{}, error) {
args := c.Mock.Called()
return args.Get(0), args.Error(1)
}
func getRedisStorage(conn redis.Conn) *RedisStorage {
pool := &redis.Pool{
MaxIdle: 3,
Dial: func() (redis.Conn, error) {
return conn, nil
},
}
return &RedisStorage{
BaseStorage{MAX_REQUEST_COUNT, BIN_LIFETIME},
pool,
"skimmer",
&time.Timer{},
}
}
func TestRedisCreateBin(t *testing.T) {
conn := &MockedRedisConn{}
storage := getRedisStorage(conn)
bin := NewBin()
if binDump, err := storage.Dump(bin); assert.Nil(t, err) {
conn.On("Send", "SET", []interface{}{
storage.getKey(BIN_KEY, bin.Name),
binDump,
},
).Return(nil).Once()
conn.On("Send", "EXPIRE", []interface{}{
storage.getKey(BIN_KEY, bin.Name),
BIN_LIFETIME,
},
).Return(nil).Once()
conn.On("Flush").Return(nil).Once()
conn.On("Do", "", []interface{}(nil)).Return(0, nil).Once()
conn.On("Err").Return(nil).Once()
err := storage.CreateBin(bin)
conn.Mock.AssertExpectations(t)
assert.Nil(t, err)
}
}
func TestRedisUpdateBin(t *testing.T) {
conn := &MockedRedisConn{}
storage := getRedisStorage(conn)
bin := NewBin()
if binDump, err := storage.Dump(bin); assert.Nil(t, err) {
conn.On("Send", "SET", []interface{}{
storage.getKey(BIN_KEY, bin.Name),
binDump,
},
).Return(nil).Once()
conn.On("Send", "EXPIRE", []interface{}{
storage.getKey(BIN_KEY, bin.Name),
BIN_LIFETIME,
},
).Return(nil).Once()
conn.On("Flush").Return(nil).Once()
conn.On("Do", "", []interface{}(nil)).Return(0, nil).Once()
conn.On("Err").Return(nil).Once()
err := storage.UpdateBin(bin)
conn.Mock.AssertExpectations(t)
assert.Nil(t, err)
// error
conn.On("Send", "SET", []interface{}{
storage.getKey(BIN_KEY, bin.Name),
binDump,
},
).Return(errors.New("error")).Once()
conn.On("Do", "", []interface{}(nil)).Return(0, nil).Once()
conn.On("Err").Return(nil).Once()
err = storage.UpdateBin(bin)
conn.Mock.AssertExpectations(t)
assert.NotNil(t, err)
}
}
func TestRedisLookupBin(t *testing.T) {
conn := &MockedRedisConn{}
storage := getRedisStorage(conn)
bin := NewBin()
if binDump, err := storage.Dump(bin); assert.Nil(t, err) {
conn.On("Do", "GET", []interface{}{storage.getKey(BIN_KEY, bin.Name)}).Return(binDump, nil).Once()
conn.On("Do", "", []interface{}(nil)).Return(0, nil).Once()
conn.On("Err").Return(nil).Once()
bin2, err := storage.LookupBin(bin.Name)
conn.Mock.AssertExpectations(t)
if assert.Nil(t, err) {
assert.Equal(t, bin2, bin)
}
// error
conn.On("Do", "GET", []interface{}{storage.getKey(BIN_KEY, bin.Name)}).Return(nil, errors.New("error")).Once()
conn.On("Do", "", []interface{}(nil)).Return(0, nil).Once()
conn.On("Err").Return(nil).Once()
bin2, err = storage.LookupBin(bin.Name)
conn.Mock.AssertExpectations(t)
assert.NotNil(t, err)
}
}
func TestRedisLookupBins(t *testing.T) {
conn := &MockedRedisConn{}
storage := getRedisStorage(conn)
bins, err := storage.LookupBins([]string{})
if assert.Nil(t, err) {
assert.Empty(t, bins)
}
bin1 := NewBin()
bin2 := NewBin()
if binDump1, err := storage.Dump(bin1); assert.Nil(t, err) {
if binDump2, err := storage.Dump(bin2); assert.Nil(t, err) {
conn.On("Do", "MGET", []interface{}{
storage.getKey(BIN_KEY, bin1.Name),
storage.getKey(BIN_KEY, bin2.Name),
}).Return([]interface{}{binDump1, binDump2}, nil).Once()
conn.On("Do", "", []interface{}(nil)).Return(0, nil).Once()
conn.On("Err").Return(nil).Once()
bins, err = storage.LookupBins([]string{bin1.Name, bin2.Name})
conn.Mock.AssertExpectations(t)
if assert.Nil(t, err) {
assert.Equal(t, len(bins), 2)
assert.Equal(t, bins[0], bin1)
assert.Equal(t, bins[1], bin2)
}
// mget error
conn.On("Do", "MGET", []interface{}{
storage.getKey(BIN_KEY, bin1.Name),
}).Return(nil, errors.New("error")).Once()
conn.On("Do", "", []interface{}(nil)).Return(0, nil).Once()
conn.On("Err").Return(nil).Once()
bins, err = storage.LookupBins([]string{bin1.Name})
conn.Mock.AssertExpectations(t)
assert.NotNil(t, err)
// slice error
conn.On("Do", "MGET", []interface{}{
storage.getKey(BIN_KEY, bin1.Name),
}).Return([]interface {}{string(binDump1)},nil).Once()
conn.On("Do", "", []interface{}(nil)).Return(0, nil).Once()
conn.On("Err").Return(nil).Once()
bins, err = storage.LookupBins([]string{bin1.Name})
conn.Mock.AssertExpectations(t)
assert.NotNil(t, err)
}
}
}
func TestRedisCreateRequest(t *testing.T) {
conn := &MockedRedisConn{}
storage := getRedisStorage(conn)
bin := NewBin()
bin.RequestCount = 1
httpRequest, _ := http.NewRequest("GET", "/", bytes.NewBuffer([]byte("body")))
req := NewRequest(httpRequest, REQUEST_BODY_SIZE)
binDump, _ := storage.Dump(bin)
if reqDump, err := storage.Dump(req); assert.Nil(t, err) {
conn.On("Send", "LPUSH", []interface{}{
storage.getKey(REQUESTS_KEY, bin.Name),
req.Id,
},
).Return(nil).Once()
conn.On("Send", "EXPIRE", []interface{}{
storage.getKey(REQUESTS_KEY, bin.Name),
BIN_LIFETIME,
},
).Return(nil).Once()
conn.On("Send", "HSET", []interface{}{
storage.getKey(REQUEST_HASH_KEY, bin.Name),
req.Id,
reqDump,
},
).Return(nil).Once()
conn.On("Send", "EXPIRE", []interface{}{
storage.getKey(REQUEST_HASH_KEY, bin.Name),
BIN_LIFETIME,
},
).Return(nil).Once()
conn.On("Flush").Return(nil).Once()
conn.On("Receive").Return(int64(1), nil).Twice()
// update bin
conn.On("Send", "SET", []interface{}{
storage.getKey(BIN_KEY, bin.Name),
binDump,
},
).Return(nil).Once()
conn.On("Send", "EXPIRE", []interface{}{
storage.getKey(BIN_KEY, bin.Name),
BIN_LIFETIME,
},
).Return(nil).Once()
conn.On("Flush").Return(nil).Once()
conn.On("Do", "", []interface{}(nil)).Return(0, nil).Twice()
conn.On("Err").Return(nil).Times(2)
err = storage.CreateRequest(bin, req)
conn.Mock.AssertExpectations(t)
assert.Nil(t, err)
}
}
func TestRedisLookupRequest(t *testing.T) {
conn := &MockedRedisConn{}
storage := getRedisStorage(conn)
bin := NewBin()
httpRequest, _ := http.NewRequest("GET", "/", bytes.NewBuffer([]byte("body")))
req := NewRequest(httpRequest, REQUEST_BODY_SIZE)
if reqDump, err := storage.Dump(req); assert.Nil(t, err) {
conn.On("Do", "HGET", []interface{}{
storage.getKey(REQUEST_HASH_KEY, bin.Name),
req.Id,
}).Return(reqDump, nil).Once()
conn.On("Do", "", []interface{}(nil)).Return(0, nil).Once()
conn.On("Err").Return(nil).Once()
req2, err := storage.LookupRequest(bin.Name, req.Id)
conn.Mock.AssertExpectations(t)
if assert.Nil(t, err) {
assert.Equal(t, req2, req)
}
// not found
conn.On("Do", "HGET", []interface{}{
storage.getKey(REQUEST_HASH_KEY, bin.Name),
"id",
}).Return(nil, nil).Once()
conn.On("Do", "", []interface{}(nil)).Return(0, nil).Once()
conn.On("Err").Return(nil).Once()
req2, err = storage.LookupRequest(bin.Name, "id")
fmt.Println(req2, err)
conn.Mock.AssertExpectations(t)
assert.NotNil(t, err)
}
}
func TestRedisLookupRequests(t *testing.T) {
conn := &MockedRedisConn{}
storage := getRedisStorage(conn)
bin := NewBin()
httpRequest, _ := http.NewRequest("GET", "/", bytes.NewBuffer([]byte("body")))
req1 := NewRequest(httpRequest, REQUEST_BODY_SIZE)
req2 := NewRequest(httpRequest, REQUEST_BODY_SIZE)
req3 := NewRequest(httpRequest, REQUEST_BODY_SIZE)
req1Dump, err1 := storage.Dump(req1)
req2Dump, err2 := storage.Dump(req2)
req3Dump, err3 := storage.Dump(req3)
if assert.Nil(t, err1) && assert.Nil(t, err2) && assert.Nil(t, err3) {
conn.On("Do", "LRANGE", []interface{}{
storage.getKey(REQUESTS_KEY, bin.Name),
0, 2 - 1,
}).Return([]interface{}{[]byte(req1.Id), []byte(req2.Id), []byte(req3.Id)}, nil).Once()
conn.On("Do", "HMGET", []interface {}{
storage.getKey(REQUEST_HASH_KEY, bin.Name),
req1.Id, req2.Id, req3.Id,
}).Return([]interface{}{req1Dump, req2Dump, req3Dump}, nil).Once()
conn.On("Do", "", []interface{}(nil)).Return(0, nil).Once()
conn.On("Err").Return(nil).Once()
requests, err := storage.LookupRequests(bin.Name, 0, 2)
conn.Mock.AssertExpectations(t)
if assert.Nil(t, err) {
assert.Equal(t, len(requests), 3)
assert.Equal(t, requests[0], req1)
assert.Equal(t, requests[1], req2)
assert.Equal(t, requests[2], req3)
}
}
}