=x.SCRIPT.id?r.text():x.DISPLAY:"text"===e&&r.size===x.DISPLAY.size?r=x.TEXT:"script"===e?r=x.SCRIPT:"scriptscript"===e&&(r=x.SCRIPTSCRIPT),r},nn=function(e,t){var r,n=rn(e.size,t.style),a=n.fracNum(),i=n.fracDen();r=t.havingStyle(a);var o=wt(e.numer,r,t);if(e.continued){var s=8.5/t.fontMetrics().ptPerEm,l=3.5/t.fontMetrics().ptPerEm;o.height=o.height0?3*m:7*m,d=t.fontMetrics().denom1):(c>0?(u=t.fontMetrics().num2,p=m):(u=t.fontMetrics().num3,p=3*m),d=t.fontMetrics().denom2),h){var w=t.fontMetrics().axisHeight;u-o.depth-(w+.5*c) 0&&(t="."===(t=e)?null:t),t};ot({type:"genfrac",names:["\\genfrac"],props:{numArgs:6,allowedInArgument:!0,argTypes:["math","math","size","text","math","math"]},handler:function(e,t){var r,n=e.parser,a=t[4],i=t[5],o=lt(t[0]),s="atom"===o.type&&"open"===o.family?sn(o.text):null,l=lt(t[1]),h="atom"===l.type&&"close"===l.family?sn(l.text):null,c=Ut(t[2],"size"),m=null;r=!!c.isBlank||(m=c.value).number>0;var u="auto",p=t[3];if("ordgroup"===p.type){if(p.body.length>0){var d=Ut(p.body[0],"textord");u=on[Number(d.text)]}}else p=Ut(p,"textord"),u=on[Number(p.text)];return{type:"genfrac",mode:n.mode,numer:a,denom:i,continued:!1,hasBarLine:r,barSize:m,leftDelim:s,rightDelim:h,size:u}},htmlBuilder:nn,mathmlBuilder:an}),ot({type:"infix",names:["\\above"],props:{numArgs:1,argTypes:["size"],infix:!0},handler:function(e,t){var r=e.parser,n=(e.funcName,e.token);return{type:"infix",mode:r.mode,replaceWith:"\\\\abovefrac",size:Ut(t[0],"size").value,token:n}}}),ot({type:"genfrac",names:["\\\\abovefrac"],props:{numArgs:3,argTypes:["math","size","math"]},handler:function(e,t){var r=e.parser,n=(e.funcName,t[0]),a=function(e){if(!e)throw new Error("Expected non-null, but got "+String(e));return e}(Ut(t[1],"infix").size),i=t[2],o=a.number>0;return{type:"genfrac",mode:r.mode,numer:n,denom:i,continued:!1,hasBarLine:o,barSize:a,leftDelim:null,rightDelim:null,size:"auto"}},htmlBuilder:nn,mathmlBuilder:an});var ln=function(e,t){var r,n,a=t.style;"supsub"===e.type?(r=e.sup?wt(e.sup,t.havingStyle(a.sup()),t):wt(e.sub,t.havingStyle(a.sub()),t),n=Ut(e.base,"horizBrace")):n=Ut(e,"horizBrace");var i,o=wt(n.base,t.havingBaseStyle(x.DISPLAY)),s=Gt(n,t);if(n.isOver?(i=Ke.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:o},{type:"kern",size:.1},{type:"elem",elem:s}]},t)).children[0].children[0].children[1].classes.push("svg-align"):(i=Ke.makeVList({positionType:"bottom",positionData:o.depth+.1+s.height,children:[{type:"elem",elem:s},{type:"kern",size:.1},{type:"elem",elem:o}]},t)).children[0].children[0].children[0].classes.push("svg-align"),r){var l=Ke.makeSpan(["mord",n.isOver?"mover":"munder"],[i],t);i=n.isOver?Ke.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:l},{type:"kern",size:.2},{type:"elem",elem:r}]},t):Ke.makeVList({positionType:"bottom",positionData:l.depth+.2+r.height+r.depth,children:[{type:"elem",elem:r},{type:"kern",size:.2},{type:"elem",elem:l}]},t)}return Ke.makeSpan(["mord",n.isOver?"mover":"munder"],[i],t)};ot({type:"horizBrace",names:["\\overbrace","\\underbrace"],props:{numArgs:1},handler:function(e,t){var r=e.parser,n=e.funcName;return{type:"horizBrace",mode:r.mode,label:n,isOver:/^\\over/.test(n),base:t[0]}},htmlBuilder:ln,mathmlBuilder:function(e,t){var r=Ft(e.label);return new Tt.MathNode(e.isOver?"mover":"munder",[Rt(e.base,t),r])}}),ot({type:"href",names:["\\href"],props:{numArgs:2,argTypes:["url","original"],allowedInText:!0},handler:function(e,t){var r=e.parser,n=t[1],a=Ut(t[0],"url").url;return r.settings.isTrusted({command:"\\href",url:a})?{type:"href",mode:r.mode,href:a,body:ht(n)}:r.formatUnsupportedCmd("\\href")},htmlBuilder:function(e,t){var r=ft(e.body,t,!1);return Ke.makeAnchor(e.href,[],r,t)},mathmlBuilder:function(e,t){var r=It(e.body,t);return r instanceof zt||(r=new zt("mrow",[r])),r.setAttribute("href",e.href),r}}),ot({type:"href",names:["\\url"],props:{numArgs:1,argTypes:["url"],allowedInText:!0},handler:function(e,t){var r=e.parser,n=Ut(t[0],"url").url;if(!r.settings.isTrusted({command:"\\url",url:n}))return r.formatUnsupportedCmd("\\url");for(var a=[],i=0;i0&&(n=P(e.totalheight,t)-r);var a=0;e.width.number>0&&(a=P(e.width,t));var i={height:F(r+n)};a>0&&(i.width=F(a)),n>0&&(i.verticalAlign=F(-n));var o=new j(e.src,e.alt,i);return o.height=r,o.depth=n,o},mathmlBuilder:function(e,t){var r=new Tt.MathNode("mglyph",[]);r.setAttribute("alt",e.alt);var n=P(e.height,t),a=0;if(e.totalheight.number>0&&(a=P(e.totalheight,t)-n,r.setAttribute("valign",F(-a))),r.setAttribute("height",F(n+a)),e.width.number>0){var i=P(e.width,t);r.setAttribute("width",F(i))}return r.setAttribute("src",e.src),r}}),ot({type:"kern",names:["\\kern","\\mkern","\\hskip","\\mskip"],props:{numArgs:1,argTypes:["size"],primitive:!0,allowedInText:!0},handler:function(e,t){var r=e.parser,n=e.funcName,a=Ut(t[0],"size");if(r.settings.strict){var i="m"===n[1],o="mu"===a.value.unit;i?(o||r.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+n+" supports only mu units, not "+a.value.unit+" units"),"math"!==r.mode&&r.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+n+" works only in math mode")):o&&r.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+n+" doesn't support mu units")}return{type:"kern",mode:r.mode,dimension:a.value}},htmlBuilder:function(e,t){return Ke.makeGlue(e.dimension,t)},mathmlBuilder:function(e,t){var r=P(e.dimension,t);return new Tt.SpaceNode(r)}}),ot({type:"lap",names:["\\mathllap","\\mathrlap","\\mathclap"],props:{numArgs:1,allowedInText:!0},handler:function(e,t){var r=e.parser,n=e.funcName,a=t[0];return{type:"lap",mode:r.mode,alignment:n.slice(5),body:a}},htmlBuilder:function(e,t){var r;"clap"===e.alignment?(r=Ke.makeSpan([],[wt(e.body,t)]),r=Ke.makeSpan(["inner"],[r],t)):r=Ke.makeSpan(["inner"],[wt(e.body,t)]);var n=Ke.makeSpan(["fix"],[]),a=Ke.makeSpan([e.alignment],[r,n],t),i=Ke.makeSpan(["strut"]);return i.style.height=F(a.height+a.depth),a.depth&&(i.style.verticalAlign=F(-a.depth)),a.children.unshift(i),a=Ke.makeSpan(["thinbox"],[a],t),Ke.makeSpan(["mord","vbox"],[a],t)},mathmlBuilder:function(e,t){var r=new Tt.MathNode("mpadded",[Rt(e.body,t)]);if("rlap"!==e.alignment){var n="llap"===e.alignment?"-1":"-0.5";r.setAttribute("lspace",n+"width")}return r.setAttribute("width","0px"),r}}),ot({type:"styling",names:["\\(","$"],props:{numArgs:0,allowedInText:!0,allowedInMath:!1},handler:function(e,t){var r=e.funcName,n=e.parser,a=n.mode;n.switchMode("math");var i="\\("===r?"\\)":"$",o=n.parseExpression(!1,i);return n.expect(i),n.switchMode(a),{type:"styling",mode:n.mode,style:"text",body:o}}}),ot({type:"text",names:["\\)","\\]"],props:{numArgs:0,allowedInText:!0,allowedInMath:!1},handler:function(e,t){throw new n("Mismatched "+e.funcName)}});var cn=function(e,t){switch(t.style.size){case x.DISPLAY.size:return e.display;case x.TEXT.size:return e.text;case x.SCRIPT.size:return e.script;case x.SCRIPTSCRIPT.size:return e.scriptscript;default:return e.text}};ot({type:"mathchoice",names:["\\mathchoice"],props:{numArgs:4,primitive:!0},handler:function(e,t){return{type:"mathchoice",mode:e.parser.mode,display:ht(t[0]),text:ht(t[1]),script:ht(t[2]),scriptscript:ht(t[3])}},htmlBuilder:function(e,t){var r=cn(e,t),n=ft(r,t,!1);return Ke.makeFragment(n)},mathmlBuilder:function(e,t){var r=cn(e,t);return It(r,t)}});var mn=function(e,t,r,n,a,i,o){e=Ke.makeSpan([],[e]);var s,h,c,m=r&&l.isCharacterBox(r);if(t){var u=wt(t,n.havingStyle(a.sup()),n);h={elem:u,kern:Math.max(n.fontMetrics().bigOpSpacing1,n.fontMetrics().bigOpSpacing3-u.depth)}}if(r){var p=wt(r,n.havingStyle(a.sub()),n);s={elem:p,kern:Math.max(n.fontMetrics().bigOpSpacing2,n.fontMetrics().bigOpSpacing4-p.height)}}if(h&&s){var d=n.fontMetrics().bigOpSpacing5+s.elem.height+s.elem.depth+s.kern+e.depth+o;c=Ke.makeVList({positionType:"bottom",positionData:d,children:[{type:"kern",size:n.fontMetrics().bigOpSpacing5},{type:"elem",elem:s.elem,marginLeft:F(-i)},{type:"kern",size:s.kern},{type:"elem",elem:e},{type:"kern",size:h.kern},{type:"elem",elem:h.elem,marginLeft:F(i)},{type:"kern",size:n.fontMetrics().bigOpSpacing5}]},n)}else if(s){var f=e.height-o;c=Ke.makeVList({positionType:"top",positionData:f,children:[{type:"kern",size:n.fontMetrics().bigOpSpacing5},{type:"elem",elem:s.elem,marginLeft:F(-i)},{type:"kern",size:s.kern},{type:"elem",elem:e}]},n)}else{if(!h)return e;var g=e.depth+o;c=Ke.makeVList({positionType:"bottom",positionData:g,children:[{type:"elem",elem:e},{type:"kern",size:h.kern},{type:"elem",elem:h.elem,marginLeft:F(i)},{type:"kern",size:n.fontMetrics().bigOpSpacing5}]},n)}var v=[c];if(s&&0!==i&&!m){var y=Ke.makeSpan(["mspace"],[],n);y.style.marginRight=F(i),v.unshift(y)}return Ke.makeSpan(["mop","op-limits"],v,n)},un=["\\smallint"],pn=function(e,t){var r,n,a,i=!1;"supsub"===e.type?(r=e.sup,n=e.sub,a=Ut(e.base,"op"),i=!0):a=Ut(e,"op");var o,s=t.style,h=!1;if(s.size===x.DISPLAY.size&&a.symbol&&!l.contains(un,a.name)&&(h=!0),a.symbol){var c=h?"Size2-Regular":"Size1-Regular",m="";if("\\oiint"!==a.name&&"\\oiiint"!==a.name||(m=a.name.slice(1),a.name="oiint"===m?"\\iint":"\\iiint"),o=Ke.makeSymbol(a.name,c,"math",t,["mop","op-symbol",h?"large-op":"small-op"]),m.length>0){var u=o.italic,p=Ke.staticSvg(m+"Size"+(h?"2":"1"),t);o=Ke.makeVList({positionType:"individualShift",children:[{type:"elem",elem:o,shift:0},{type:"elem",elem:p,shift:h?.08:0}]},t),a.name="\\"+m,o.classes.unshift("mop"),o.italic=u}}else if(a.body){var d=ft(a.body,t,!0);1===d.length&&d[0]instanceof Z?(o=d[0]).classes[0]="mop":o=Ke.makeSpan(["mop"],d,t)}else{for(var f=[],g=1;g0){for(var s=a.body.map((function(e){var t=e.text;return"string"==typeof t?{type:"textord",mode:e.mode,text:t}:e})),l=ft(s,t.withFont("mathrm"),!0),h=0;h=0?s.setAttribute("height",F(a)):(s.setAttribute("height",F(a)),s.setAttribute("depth",F(-a))),s.setAttribute("voffset",F(a)),s}});var bn=["\\tiny","\\sixptsize","\\scriptsize","\\footnotesize","\\small","\\normalsize","\\large","\\Large","\\LARGE","\\huge","\\Huge"];ot({type:"sizing",names:bn,props:{numArgs:0,allowedInText:!0},handler:function(e,t){var r=e.breakOnTokenText,n=e.funcName,a=e.parser,i=a.parseExpression(!1,r);return{type:"sizing",mode:a.mode,size:bn.indexOf(n)+1,body:i}},htmlBuilder:function(e,t){var r=t.havingSize(e.size);return yn(e.body,r,t)},mathmlBuilder:function(e,t){var r=t.havingSize(e.size),n=qt(e.body,r),a=new Tt.MathNode("mstyle",n);return a.setAttribute("mathsize",F(r.sizeMultiplier)),a}}),ot({type:"smash",names:["\\smash"],props:{numArgs:1,numOptionalArgs:1,allowedInText:!0},handler:function(e,t,r){var n=e.parser,a=!1,i=!1,o=r[0]&&Ut(r[0],"ordgroup");if(o)for(var s="",l=0;lr.height+r.depth+i&&(i=(i+m-r.height-r.depth)/2);var u=l.height-r.height-i-h;r.style.paddingLeft=F(c);var p=Ke.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:r,wrapperClasses:["svg-align"]},{type:"kern",size:-(r.height+u)},{type:"elem",elem:l},{type:"kern",size:h}]},t);if(e.index){var d=t.havingStyle(x.SCRIPTSCRIPT),f=wt(e.index,d,t),g=.6*(p.height-p.depth),v=Ke.makeVList({positionType:"shift",positionData:-g,children:[{type:"elem",elem:f}]},t),y=Ke.makeSpan(["root"],[v]);return Ke.makeSpan(["mord","sqrt"],[y,p],t)}return Ke.makeSpan(["mord","sqrt"],[p],t)},mathmlBuilder:function(e,t){var r=e.body,n=e.index;return n?new Tt.MathNode("mroot",[Rt(r,t),Rt(n,t)]):new Tt.MathNode("msqrt",[Rt(r,t)])}});var xn={display:x.DISPLAY,text:x.TEXT,script:x.SCRIPT,scriptscript:x.SCRIPTSCRIPT};ot({type:"styling",names:["\\displaystyle","\\textstyle","\\scriptstyle","\\scriptscriptstyle"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler:function(e,t){var r=e.breakOnTokenText,n=e.funcName,a=e.parser,i=a.parseExpression(!0,r),o=n.slice(1,n.length-5);return{type:"styling",mode:a.mode,style:o,body:i}},htmlBuilder:function(e,t){var r=xn[e.style],n=t.havingStyle(r).withFont("");return yn(e.body,n,t)},mathmlBuilder:function(e,t){var r=xn[e.style],n=t.havingStyle(r),a=qt(e.body,n),i=new Tt.MathNode("mstyle",a),o={display:["0","true"],text:["0","false"],script:["1","false"],scriptscript:["2","false"]}[e.style];return i.setAttribute("scriptlevel",o[0]),i.setAttribute("displaystyle",o[1]),i}});var wn=function(e,t){var r=e.base;return r?"op"===r.type?r.limits&&(t.style.size===x.DISPLAY.size||r.alwaysHandleSupSub)?pn:null:"operatorname"===r.type?r.alwaysHandleSupSub&&(t.style.size===x.DISPLAY.size||r.limits)?vn:null:"accent"===r.type?l.isCharacterBox(r.base)?Wt:null:"horizBrace"===r.type&&!e.sub===r.isOver?ln:null:null};st({type:"supsub",htmlBuilder:function(e,t){var r=wn(e,t);if(r)return r(e,t);var n,a,i,o=e.base,s=e.sup,h=e.sub,c=wt(o,t),m=t.fontMetrics(),u=0,p=0,d=o&&l.isCharacterBox(o);if(s){var f=t.havingStyle(t.style.sup());n=wt(s,f,t),d||(u=c.height-f.fontMetrics().supDrop*f.sizeMultiplier/t.sizeMultiplier)}if(h){var g=t.havingStyle(t.style.sub());a=wt(h,g,t),d||(p=c.depth+g.fontMetrics().subDrop*g.sizeMultiplier/t.sizeMultiplier)}i=t.style===x.DISPLAY?m.sup1:t.style.cramped?m.sup3:m.sup2;var v,y=t.sizeMultiplier,b=F(.5/m.ptPerEm/y),w=null;if(a){var k=e.base&&"op"===e.base.type&&e.base.name&&("\\oiint"===e.base.name||"\\oiiint"===e.base.name);(c instanceof Z||k)&&(w=F(-c.italic))}if(n&&a){u=Math.max(u,i,n.depth+.25*m.xHeight),p=Math.max(p,m.sub2);var S=4*m.defaultRuleThickness;if(u-n.depth-(a.height-p)0&&(u+=M,p-=M)}var z=[{type:"elem",elem:a,shift:p,marginRight:b,marginLeft:w},{type:"elem",elem:n,shift:-u,marginRight:b}];v=Ke.makeVList({positionType:"individualShift",children:z},t)}else if(a){p=Math.max(p,m.sub1,a.height-.8*m.xHeight);var A=[{type:"elem",elem:a,marginLeft:w,marginRight:b}];v=Ke.makeVList({positionType:"shift",positionData:p,children:A},t)}else{if(!n)throw new Error("supsub must have either sup or sub.");u=Math.max(u,i,n.depth+.25*m.xHeight),v=Ke.makeVList({positionType:"shift",positionData:-u,children:[{type:"elem",elem:n,marginRight:b}]},t)}var T=bt(c,"right")||"mord";return Ke.makeSpan([T],[c,Ke.makeSpan(["msupsub"],[v])],t)},mathmlBuilder:function(e,t){var r,n=!1;e.base&&"horizBrace"===e.base.type&&!!e.sup===e.base.isOver&&(n=!0,r=e.base.isOver),!e.base||"op"!==e.base.type&&"operatorname"!==e.base.type||(e.base.parentIsSupSub=!0);var a,i=[Rt(e.base,t)];if(e.sub&&i.push(Rt(e.sub,t)),e.sup&&i.push(Rt(e.sup,t)),n)a=r?"mover":"munder";else if(e.sub)if(e.sup){var o=e.base;a=o&&"op"===o.type&&o.limits&&t.style===x.DISPLAY||o&&"operatorname"===o.type&&o.alwaysHandleSupSub&&(t.style===x.DISPLAY||o.limits)?"munderover":"msubsup"}else{var s=e.base;a=s&&"op"===s.type&&s.limits&&(t.style===x.DISPLAY||s.alwaysHandleSupSub)||s&&"operatorname"===s.type&&s.alwaysHandleSupSub&&(s.limits||t.style===x.DISPLAY)?"munder":"msub"}else{var l=e.base;a=l&&"op"===l.type&&l.limits&&(t.style===x.DISPLAY||l.alwaysHandleSupSub)||l&&"operatorname"===l.type&&l.alwaysHandleSupSub&&(l.limits||t.style===x.DISPLAY)?"mover":"msup"}return new Tt.MathNode(a,i)}}),st({type:"atom",htmlBuilder:function(e,t){return Ke.mathsym(e.text,e.mode,t,["m"+e.family])},mathmlBuilder:function(e,t){var r=new Tt.MathNode("mo",[Bt(e.text,e.mode)]);if("bin"===e.family){var n=Nt(e,t);"bold-italic"===n&&r.setAttribute("mathvariant",n)}else"punct"===e.family?r.setAttribute("separator","true"):"open"!==e.family&&"close"!==e.family||r.setAttribute("stretchy","false");return r}});var kn={mi:"italic",mn:"normal",mtext:"normal"};st({type:"mathord",htmlBuilder:function(e,t){return Ke.makeOrd(e,t,"mathord")},mathmlBuilder:function(e,t){var r=new Tt.MathNode("mi",[Bt(e.text,e.mode,t)]),n=Nt(e,t)||"italic";return n!==kn[r.type]&&r.setAttribute("mathvariant",n),r}}),st({type:"textord",htmlBuilder:function(e,t){return Ke.makeOrd(e,t,"textord")},mathmlBuilder:function(e,t){var r,n=Bt(e.text,e.mode,t),a=Nt(e,t)||"normal";return r="text"===e.mode?new Tt.MathNode("mtext",[n]):/[0-9]/.test(e.text)?new Tt.MathNode("mn",[n]):"\\prime"===e.text?new Tt.MathNode("mo",[n]):new Tt.MathNode("mi",[n]),a!==kn[r.type]&&r.setAttribute("mathvariant",a),r}});var Sn={"\\nobreak":"nobreak","\\allowbreak":"allowbreak"},Mn={" ":{},"\\ ":{},"~":{className:"nobreak"},"\\space":{},"\\nobreakspace":{className:"nobreak"}};st({type:"spacing",htmlBuilder:function(e,t){if(Mn.hasOwnProperty(e.text)){var r=Mn[e.text].className||"";if("text"===e.mode){var a=Ke.makeOrd(e,t,"textord");return a.classes.push(r),a}return Ke.makeSpan(["mspace",r],[Ke.mathsym(e.text,e.mode,t)],t)}if(Sn.hasOwnProperty(e.text))return Ke.makeSpan(["mspace",Sn[e.text]],[],t);throw new n('Unknown type of space "'+e.text+'"')},mathmlBuilder:function(e,t){if(!Mn.hasOwnProperty(e.text)){if(Sn.hasOwnProperty(e.text))return new Tt.MathNode("mspace");throw new n('Unknown type of space "'+e.text+'"')}return new Tt.MathNode("mtext",[new Tt.TextNode("\xa0")])}});var zn=function(){var e=new Tt.MathNode("mtd",[]);return e.setAttribute("width","50%"),e};st({type:"tag",mathmlBuilder:function(e,t){var r=new Tt.MathNode("mtable",[new Tt.MathNode("mtr",[zn(),new Tt.MathNode("mtd",[It(e.body,t)]),zn(),new Tt.MathNode("mtd",[It(e.tag,t)])])]);return r.setAttribute("width","100%"),r}});var An={"\\text":void 0,"\\textrm":"textrm","\\textsf":"textsf","\\texttt":"texttt","\\textnormal":"textrm"},Tn={"\\textbf":"textbf","\\textmd":"textmd"},Bn={"\\textit":"textit","\\textup":"textup"},Cn=function(e,t){var r=e.font;return r?An[r]?t.withTextFontFamily(An[r]):Tn[r]?t.withTextFontWeight(Tn[r]):t.withTextFontShape(Bn[r]):t};ot({type:"text",names:["\\text","\\textrm","\\textsf","\\texttt","\\textnormal","\\textbf","\\textmd","\\textit","\\textup"],props:{numArgs:1,argTypes:["text"],allowedInArgument:!0,allowedInText:!0},handler:function(e,t){var r=e.parser,n=e.funcName,a=t[0];return{type:"text",mode:r.mode,body:ht(a),font:n}},htmlBuilder:function(e,t){var r=Cn(e,t),n=ft(e.body,r,!0);return Ke.makeSpan(["mord","text"],n,r)},mathmlBuilder:function(e,t){var r=Cn(e,t);return It(e.body,r)}}),ot({type:"underline",names:["\\underline"],props:{numArgs:1,allowedInText:!0},handler:function(e,t){return{type:"underline",mode:e.parser.mode,body:t[0]}},htmlBuilder:function(e,t){var r=wt(e.body,t),n=Ke.makeLineSpan("underline-line",t),a=t.fontMetrics().defaultRuleThickness,i=Ke.makeVList({positionType:"top",positionData:r.height,children:[{type:"kern",size:a},{type:"elem",elem:n},{type:"kern",size:3*a},{type:"elem",elem:r}]},t);return Ke.makeSpan(["mord","underline"],[i],t)},mathmlBuilder:function(e,t){var r=new Tt.MathNode("mo",[new Tt.TextNode("\u203e")]);r.setAttribute("stretchy","true");var n=new Tt.MathNode("munder",[Rt(e.body,t),r]);return n.setAttribute("accentunder","true"),n}}),ot({type:"vcenter",names:["\\vcenter"],props:{numArgs:1,argTypes:["original"],allowedInText:!1},handler:function(e,t){return{type:"vcenter",mode:e.parser.mode,body:t[0]}},htmlBuilder:function(e,t){var r=wt(e.body,t),n=t.fontMetrics().axisHeight,a=.5*(r.height-n-(r.depth+n));return Ke.makeVList({positionType:"shift",positionData:a,children:[{type:"elem",elem:r}]},t)},mathmlBuilder:function(e,t){return new Tt.MathNode("mpadded",[Rt(e.body,t)],["vcenter"])}}),ot({type:"verb",names:["\\verb"],props:{numArgs:0,allowedInText:!0},handler:function(e,t,r){throw new n("\\verb ended by end of line instead of matching delimiter")},htmlBuilder:function(e,t){for(var r=Nn(e),n=[],a=t.havingStyle(t.style.text()),i=0;i0;)this.endGroup()},t.has=function(e){return this.current.hasOwnProperty(e)||this.builtins.hasOwnProperty(e)},t.get=function(e){return this.current.hasOwnProperty(e)?this.current[e]:this.builtins[e]},t.set=function(e,t,r){if(void 0===r&&(r=!1),r){for(var n=0;n0&&(this.undefStack[this.undefStack.length-1][e]=t)}else{var a=this.undefStack[this.undefStack.length-1];a&&!a.hasOwnProperty(e)&&(a[e]=this.current[e])}null==t?delete this.current[e]:this.current[e]=t},e}(),On=Vr;Pr("\\noexpand",(function(e){var t=e.popToken();return e.isExpandable(t.text)&&(t.noexpand=!0,t.treatAsRelax=!0),{tokens:[t],numArgs:0}})),Pr("\\expandafter",(function(e){var t=e.popToken();return e.expandOnce(!0),{tokens:[t],numArgs:0}})),Pr("\\@firstoftwo",(function(e){return{tokens:e.consumeArgs(2)[0],numArgs:0}})),Pr("\\@secondoftwo",(function(e){return{tokens:e.consumeArgs(2)[1],numArgs:0}})),Pr("\\@ifnextchar",(function(e){var t=e.consumeArgs(3);e.consumeSpaces();var r=e.future();return 1===t[0].length&&t[0][0].text===r.text?{tokens:t[1],numArgs:0}:{tokens:t[2],numArgs:0}})),Pr("\\@ifstar","\\@ifnextchar *{\\@firstoftwo{#1}}"),Pr("\\TextOrMath",(function(e){var t=e.consumeArgs(2);return"text"===e.mode?{tokens:t[0],numArgs:0}:{tokens:t[1],numArgs:0}}));var En={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,a:10,A:10,b:11,B:11,c:12,C:12,d:13,D:13,e:14,E:14,f:15,F:15};Pr("\\char",(function(e){var t,r=e.popToken(),a="";if("'"===r.text)t=8,r=e.popToken();else if('"'===r.text)t=16,r=e.popToken();else if("`"===r.text)if("\\"===(r=e.popToken()).text[0])a=r.text.charCodeAt(1);else{if("EOF"===r.text)throw new n("\\char` missing argument");a=r.text.charCodeAt(0)}else t=10;if(t){if(null==(a=En[r.text])||a>=t)throw new n("Invalid base-"+t+" digit "+r.text);for(var i;null!=(i=En[e.future().text])&&i":"\\dotsb","-":"\\dotsb","*":"\\dotsb",":":"\\dotsb","\\DOTSB":"\\dotsb","\\coprod":"\\dotsb","\\bigvee":"\\dotsb","\\bigwedge":"\\dotsb","\\biguplus":"\\dotsb","\\bigcap":"\\dotsb","\\bigcup":"\\dotsb","\\prod":"\\dotsb","\\sum":"\\dotsb","\\bigotimes":"\\dotsb","\\bigoplus":"\\dotsb","\\bigodot":"\\dotsb","\\bigsqcup":"\\dotsb","\\And":"\\dotsb","\\longrightarrow":"\\dotsb","\\Longrightarrow":"\\dotsb","\\longleftarrow":"\\dotsb","\\Longleftarrow":"\\dotsb","\\longleftrightarrow":"\\dotsb","\\Longleftrightarrow":"\\dotsb","\\mapsto":"\\dotsb","\\longmapsto":"\\dotsb","\\hookrightarrow":"\\dotsb","\\doteq":"\\dotsb","\\mathbin":"\\dotsb","\\mathrel":"\\dotsb","\\relbar":"\\dotsb","\\Relbar":"\\dotsb","\\xrightarrow":"\\dotsb","\\xleftarrow":"\\dotsb","\\DOTSI":"\\dotsi","\\int":"\\dotsi","\\oint":"\\dotsi","\\iint":"\\dotsi","\\iiint":"\\dotsi","\\iiiint":"\\dotsi","\\idotsint":"\\dotsi","\\DOTSX":"\\dotsx"};Pr("\\dots",(function(e){var t="\\dotso",r=e.expandAfterFuture().text;return r in Dn?t=Dn[r]:("\\not"===r.slice(0,4)||r in ae.math&&l.contains(["bin","rel"],ae.math[r].group))&&(t="\\dotsb"),t}));var Vn={")":!0,"]":!0,"\\rbrack":!0,"\\}":!0,"\\rbrace":!0,"\\rangle":!0,"\\rceil":!0,"\\rfloor":!0,"\\rgroup":!0,"\\rmoustache":!0,"\\right":!0,"\\bigr":!0,"\\biggr":!0,"\\Bigr":!0,"\\Biggr":!0,$:!0,";":!0,".":!0,",":!0};Pr("\\dotso",(function(e){return e.future().text in Vn?"\\ldots\\,":"\\ldots"})),Pr("\\dotsc",(function(e){var t=e.future().text;return t in Vn&&","!==t?"\\ldots\\,":"\\ldots"})),Pr("\\cdots",(function(e){return e.future().text in Vn?"\\@cdots\\,":"\\@cdots"})),Pr("\\dotsb","\\cdots"),Pr("\\dotsm","\\cdots"),Pr("\\dotsi","\\!\\cdots"),Pr("\\dotsx","\\ldots\\,"),Pr("\\DOTSI","\\relax"),Pr("\\DOTSB","\\relax"),Pr("\\DOTSX","\\relax"),Pr("\\tmspace","\\TextOrMath{\\kern#1#3}{\\mskip#1#2}\\relax"),Pr("\\,","\\tmspace+{3mu}{.1667em}"),Pr("\\thinspace","\\,"),Pr("\\>","\\mskip{4mu}"),Pr("\\:","\\tmspace+{4mu}{.2222em}"),Pr("\\medspace","\\:"),Pr("\\;","\\tmspace+{5mu}{.2777em}"),Pr("\\thickspace","\\;"),Pr("\\!","\\tmspace-{3mu}{.1667em}"),Pr("\\negthinspace","\\!"),Pr("\\negmedspace","\\tmspace-{4mu}{.2222em}"),Pr("\\negthickspace","\\tmspace-{5mu}{.277em}"),Pr("\\enspace","\\kern.5em "),Pr("\\enskip","\\hskip.5em\\relax"),Pr("\\quad","\\hskip1em\\relax"),Pr("\\qquad","\\hskip2em\\relax"),Pr("\\tag","\\@ifstar\\tag@literal\\tag@paren"),Pr("\\tag@paren","\\tag@literal{({#1})}"),Pr("\\tag@literal",(function(e){if(e.macros.get("\\df@tag"))throw new n("Multiple \\tag");return"\\gdef\\df@tag{\\text{#1}}"})),Pr("\\bmod","\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}\\mathbin{\\rm mod}\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}"),Pr("\\pod","\\allowbreak\\mathchoice{\\mkern18mu}{\\mkern8mu}{\\mkern8mu}{\\mkern8mu}(#1)"),Pr("\\pmod","\\pod{{\\rm mod}\\mkern6mu#1}"),Pr("\\mod","\\allowbreak\\mathchoice{\\mkern18mu}{\\mkern12mu}{\\mkern12mu}{\\mkern12mu}{\\rm mod}\\,\\,#1"),Pr("\\newline","\\\\\\relax"),Pr("\\TeX","\\textrm{\\html@mathml{T\\kern-.1667em\\raisebox{-.5ex}{E}\\kern-.125emX}{TeX}}");var Pn=F(T["Main-Regular"]["T".charCodeAt(0)][1]-.7*T["Main-Regular"]["A".charCodeAt(0)][1]);Pr("\\LaTeX","\\textrm{\\html@mathml{L\\kern-.36em\\raisebox{"+Pn+"}{\\scriptstyle A}\\kern-.15em\\TeX}{LaTeX}}"),Pr("\\KaTeX","\\textrm{\\html@mathml{K\\kern-.17em\\raisebox{"+Pn+"}{\\scriptstyle A}\\kern-.15em\\TeX}{KaTeX}}"),Pr("\\hspace","\\@ifstar\\@hspacer\\@hspace"),Pr("\\@hspace","\\hskip #1\\relax"),Pr("\\@hspacer","\\rule{0pt}{0pt}\\hskip #1\\relax"),Pr("\\ordinarycolon",":"),Pr("\\vcentcolon","\\mathrel{\\mathop\\ordinarycolon}"),Pr("\\dblcolon",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-.9mu}\\vcentcolon}}{\\mathop{\\char"2237}}'),Pr("\\coloneqq",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}=}}{\\mathop{\\char"2254}}'),Pr("\\Coloneqq",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}=}}{\\mathop{\\char"2237\\char"3d}}'),Pr("\\coloneq",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}{\\mathop{\\char"3a\\char"2212}}'),Pr("\\Coloneq",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}{\\mathop{\\char"2237\\char"2212}}'),Pr("\\eqqcolon",'\\html@mathml{\\mathrel{=\\mathrel{\\mkern-1.2mu}\\vcentcolon}}{\\mathop{\\char"2255}}'),Pr("\\Eqqcolon",'\\html@mathml{\\mathrel{=\\mathrel{\\mkern-1.2mu}\\dblcolon}}{\\mathop{\\char"3d\\char"2237}}'),Pr("\\eqcolon",'\\html@mathml{\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\vcentcolon}}{\\mathop{\\char"2239}}'),Pr("\\Eqcolon",'\\html@mathml{\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\dblcolon}}{\\mathop{\\char"2212\\char"2237}}'),Pr("\\colonapprox",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\approx}}{\\mathop{\\char"3a\\char"2248}}'),Pr("\\Colonapprox",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\approx}}{\\mathop{\\char"2237\\char"2248}}'),Pr("\\colonsim",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\sim}}{\\mathop{\\char"3a\\char"223c}}'),Pr("\\Colonsim",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\sim}}{\\mathop{\\char"2237\\char"223c}}'),Pr("\u2237","\\dblcolon"),Pr("\u2239","\\eqcolon"),Pr("\u2254","\\coloneqq"),Pr("\u2255","\\eqqcolon"),Pr("\u2a74","\\Coloneqq"),Pr("\\ratio","\\vcentcolon"),Pr("\\coloncolon","\\dblcolon"),Pr("\\colonequals","\\coloneqq"),Pr("\\coloncolonequals","\\Coloneqq"),Pr("\\equalscolon","\\eqqcolon"),Pr("\\equalscoloncolon","\\Eqqcolon"),Pr("\\colonminus","\\coloneq"),Pr("\\coloncolonminus","\\Coloneq"),Pr("\\minuscolon","\\eqcolon"),Pr("\\minuscoloncolon","\\Eqcolon"),Pr("\\coloncolonapprox","\\Colonapprox"),Pr("\\coloncolonsim","\\Colonsim"),Pr("\\simcolon","\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\vcentcolon}"),Pr("\\simcoloncolon","\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\dblcolon}"),Pr("\\approxcolon","\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\vcentcolon}"),Pr("\\approxcoloncolon","\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\dblcolon}"),Pr("\\notni","\\html@mathml{\\not\\ni}{\\mathrel{\\char`\u220c}}"),Pr("\\limsup","\\DOTSB\\operatorname*{lim\\,sup}"),Pr("\\liminf","\\DOTSB\\operatorname*{lim\\,inf}"),Pr("\\injlim","\\DOTSB\\operatorname*{inj\\,lim}"),Pr("\\projlim","\\DOTSB\\operatorname*{proj\\,lim}"),Pr("\\varlimsup","\\DOTSB\\operatorname*{\\overline{lim}}"),Pr("\\varliminf","\\DOTSB\\operatorname*{\\underline{lim}}"),Pr("\\varinjlim","\\DOTSB\\operatorname*{\\underrightarrow{lim}}"),Pr("\\varprojlim","\\DOTSB\\operatorname*{\\underleftarrow{lim}}"),Pr("\\gvertneqq","\\html@mathml{\\@gvertneqq}{\u2269}"),Pr("\\lvertneqq","\\html@mathml{\\@lvertneqq}{\u2268}"),Pr("\\ngeqq","\\html@mathml{\\@ngeqq}{\u2271}"),Pr("\\ngeqslant","\\html@mathml{\\@ngeqslant}{\u2271}"),Pr("\\nleqq","\\html@mathml{\\@nleqq}{\u2270}"),Pr("\\nleqslant","\\html@mathml{\\@nleqslant}{\u2270}"),Pr("\\nshortmid","\\html@mathml{\\@nshortmid}{\u2224}"),Pr("\\nshortparallel","\\html@mathml{\\@nshortparallel}{\u2226}"),Pr("\\nsubseteqq","\\html@mathml{\\@nsubseteqq}{\u2288}"),Pr("\\nsupseteqq","\\html@mathml{\\@nsupseteqq}{\u2289}"),Pr("\\varsubsetneq","\\html@mathml{\\@varsubsetneq}{\u228a}"),Pr("\\varsubsetneqq","\\html@mathml{\\@varsubsetneqq}{\u2acb}"),Pr("\\varsupsetneq","\\html@mathml{\\@varsupsetneq}{\u228b}"),Pr("\\varsupsetneqq","\\html@mathml{\\@varsupsetneqq}{\u2acc}"),Pr("\\imath","\\html@mathml{\\@imath}{\u0131}"),Pr("\\jmath","\\html@mathml{\\@jmath}{\u0237}"),Pr("\\llbracket","\\html@mathml{\\mathopen{[\\mkern-3.2mu[}}{\\mathopen{\\char`\u27e6}}"),Pr("\\rrbracket","\\html@mathml{\\mathclose{]\\mkern-3.2mu]}}{\\mathclose{\\char`\u27e7}}"),Pr("\u27e6","\\llbracket"),Pr("\u27e7","\\rrbracket"),Pr("\\lBrace","\\html@mathml{\\mathopen{\\{\\mkern-3.2mu[}}{\\mathopen{\\char`\u2983}}"),Pr("\\rBrace","\\html@mathml{\\mathclose{]\\mkern-3.2mu\\}}}{\\mathclose{\\char`\u2984}}"),Pr("\u2983","\\lBrace"),Pr("\u2984","\\rBrace"),Pr("\\minuso","\\mathbin{\\html@mathml{{\\mathrlap{\\mathchoice{\\kern{0.145em}}{\\kern{0.145em}}{\\kern{0.1015em}}{\\kern{0.0725em}}\\circ}{-}}}{\\char`\u29b5}}"),Pr("\u29b5","\\minuso"),Pr("\\darr","\\downarrow"),Pr("\\dArr","\\Downarrow"),Pr("\\Darr","\\Downarrow"),Pr("\\lang","\\langle"),Pr("\\rang","\\rangle"),Pr("\\uarr","\\uparrow"),Pr("\\uArr","\\Uparrow"),Pr("\\Uarr","\\Uparrow"),Pr("\\N","\\mathbb{N}"),Pr("\\R","\\mathbb{R}"),Pr("\\Z","\\mathbb{Z}"),Pr("\\alef","\\aleph"),Pr("\\alefsym","\\aleph"),Pr("\\Alpha","\\mathrm{A}"),Pr("\\Beta","\\mathrm{B}"),Pr("\\bull","\\bullet"),Pr("\\Chi","\\mathrm{X}"),Pr("\\clubs","\\clubsuit"),Pr("\\cnums","\\mathbb{C}"),Pr("\\Complex","\\mathbb{C}"),Pr("\\Dagger","\\ddagger"),Pr("\\diamonds","\\diamondsuit"),Pr("\\empty","\\emptyset"),Pr("\\Epsilon","\\mathrm{E}"),Pr("\\Eta","\\mathrm{H}"),Pr("\\exist","\\exists"),Pr("\\harr","\\leftrightarrow"),Pr("\\hArr","\\Leftrightarrow"),Pr("\\Harr","\\Leftrightarrow"),Pr("\\hearts","\\heartsuit"),Pr("\\image","\\Im"),Pr("\\infin","\\infty"),Pr("\\Iota","\\mathrm{I}"),Pr("\\isin","\\in"),Pr("\\Kappa","\\mathrm{K}"),Pr("\\larr","\\leftarrow"),Pr("\\lArr","\\Leftarrow"),Pr("\\Larr","\\Leftarrow"),Pr("\\lrarr","\\leftrightarrow"),Pr("\\lrArr","\\Leftrightarrow"),Pr("\\Lrarr","\\Leftrightarrow"),Pr("\\Mu","\\mathrm{M}"),Pr("\\natnums","\\mathbb{N}"),Pr("\\Nu","\\mathrm{N}"),Pr("\\Omicron","\\mathrm{O}"),Pr("\\plusmn","\\pm"),Pr("\\rarr","\\rightarrow"),Pr("\\rArr","\\Rightarrow"),Pr("\\Rarr","\\Rightarrow"),Pr("\\real","\\Re"),Pr("\\reals","\\mathbb{R}"),Pr("\\Reals","\\mathbb{R}"),Pr("\\Rho","\\mathrm{P}"),Pr("\\sdot","\\cdot"),Pr("\\sect","\\S"),Pr("\\spades","\\spadesuit"),Pr("\\sub","\\subset"),Pr("\\sube","\\subseteq"),Pr("\\supe","\\supseteq"),Pr("\\Tau","\\mathrm{T}"),Pr("\\thetasym","\\vartheta"),Pr("\\weierp","\\wp"),Pr("\\Zeta","\\mathrm{Z}"),Pr("\\argmin","\\DOTSB\\operatorname*{arg\\,min}"),Pr("\\argmax","\\DOTSB\\operatorname*{arg\\,max}"),Pr("\\plim","\\DOTSB\\mathop{\\operatorname{plim}}\\limits"),Pr("\\bra","\\mathinner{\\langle{#1}|}"),Pr("\\ket","\\mathinner{|{#1}\\rangle}"),Pr("\\braket","\\mathinner{\\langle{#1}\\rangle}"),Pr("\\Bra","\\left\\langle#1\\right|"),Pr("\\Ket","\\left|#1\\right\\rangle");var Fn=function(e){return function(t){var r=t.consumeArg().tokens,n=t.consumeArg().tokens,a=t.consumeArg().tokens,i=t.consumeArg().tokens,o=t.macros.get("|"),s=t.macros.get("\\|");t.macros.beginGroup();var l=function(t){return function(r){e&&(r.macros.set("|",o),a.length&&r.macros.set("\\|",s));var i=t;!t&&a.length&&("|"===r.future().text&&(r.popToken(),i=!0));return{tokens:i?a:n,numArgs:0}}};t.macros.set("|",l(!1)),a.length&&t.macros.set("\\|",l(!0));var h=t.consumeArg().tokens,c=t.expandTokens([].concat(i,h,r));return t.macros.endGroup(),{tokens:c.reverse(),numArgs:0}}};Pr("\\bra@ket",Fn(!1)),Pr("\\bra@set",Fn(!0)),Pr("\\Braket","\\bra@ket{\\left\\langle}{\\,\\middle\\vert\\,}{\\,\\middle\\vert\\,}{\\right\\rangle}"),Pr("\\Set","\\bra@set{\\left\\{\\:}{\\;\\middle\\vert\\;}{\\;\\middle\\Vert\\;}{\\:\\right\\}}"),Pr("\\set","\\bra@set{\\{\\,}{\\mid}{}{\\,\\}}"),Pr("\\angln","{\\angl n}"),Pr("\\blue","\\textcolor{##6495ed}{#1}"),Pr("\\orange","\\textcolor{##ffa500}{#1}"),Pr("\\pink","\\textcolor{##ff00af}{#1}"),Pr("\\red","\\textcolor{##df0030}{#1}"),Pr("\\green","\\textcolor{##28ae7b}{#1}"),Pr("\\gray","\\textcolor{gray}{#1}"),Pr("\\purple","\\textcolor{##9d38bd}{#1}"),Pr("\\blueA","\\textcolor{##ccfaff}{#1}"),Pr("\\blueB","\\textcolor{##80f6ff}{#1}"),Pr("\\blueC","\\textcolor{##63d9ea}{#1}"),Pr("\\blueD","\\textcolor{##11accd}{#1}"),Pr("\\blueE","\\textcolor{##0c7f99}{#1}"),Pr("\\tealA","\\textcolor{##94fff5}{#1}"),Pr("\\tealB","\\textcolor{##26edd5}{#1}"),Pr("\\tealC","\\textcolor{##01d1c1}{#1}"),Pr("\\tealD","\\textcolor{##01a995}{#1}"),Pr("\\tealE","\\textcolor{##208170}{#1}"),Pr("\\greenA","\\textcolor{##b6ffb0}{#1}"),Pr("\\greenB","\\textcolor{##8af281}{#1}"),Pr("\\greenC","\\textcolor{##74cf70}{#1}"),Pr("\\greenD","\\textcolor{##1fab54}{#1}"),Pr("\\greenE","\\textcolor{##0d923f}{#1}"),Pr("\\goldA","\\textcolor{##ffd0a9}{#1}"),Pr("\\goldB","\\textcolor{##ffbb71}{#1}"),Pr("\\goldC","\\textcolor{##ff9c39}{#1}"),Pr("\\goldD","\\textcolor{##e07d10}{#1}"),Pr("\\goldE","\\textcolor{##a75a05}{#1}"),Pr("\\redA","\\textcolor{##fca9a9}{#1}"),Pr("\\redB","\\textcolor{##ff8482}{#1}"),Pr("\\redC","\\textcolor{##f9685d}{#1}"),Pr("\\redD","\\textcolor{##e84d39}{#1}"),Pr("\\redE","\\textcolor{##bc2612}{#1}"),Pr("\\maroonA","\\textcolor{##ffbde0}{#1}"),Pr("\\maroonB","\\textcolor{##ff92c6}{#1}"),Pr("\\maroonC","\\textcolor{##ed5fa6}{#1}"),Pr("\\maroonD","\\textcolor{##ca337c}{#1}"),Pr("\\maroonE","\\textcolor{##9e034e}{#1}"),Pr("\\purpleA","\\textcolor{##ddd7ff}{#1}"),Pr("\\purpleB","\\textcolor{##c6b9fc}{#1}"),Pr("\\purpleC","\\textcolor{##aa87ff}{#1}"),Pr("\\purpleD","\\textcolor{##7854ab}{#1}"),Pr("\\purpleE","\\textcolor{##543b78}{#1}"),Pr("\\mintA","\\textcolor{##f5f9e8}{#1}"),Pr("\\mintB","\\textcolor{##edf2df}{#1}"),Pr("\\mintC","\\textcolor{##e0e5cc}{#1}"),Pr("\\grayA","\\textcolor{##f6f7f7}{#1}"),Pr("\\grayB","\\textcolor{##f0f1f2}{#1}"),Pr("\\grayC","\\textcolor{##e3e5e6}{#1}"),Pr("\\grayD","\\textcolor{##d6d8da}{#1}"),Pr("\\grayE","\\textcolor{##babec2}{#1}"),Pr("\\grayF","\\textcolor{##888d93}{#1}"),Pr("\\grayG","\\textcolor{##626569}{#1}"),Pr("\\grayH","\\textcolor{##3b3e40}{#1}"),Pr("\\grayI","\\textcolor{##21242c}{#1}"),Pr("\\kaBlue","\\textcolor{##314453}{#1}"),Pr("\\kaGreen","\\textcolor{##71B307}{#1}");var Gn={"^":!0,_:!0,"\\limits":!0,"\\nolimits":!0},Un=function(){function e(e,t,r){this.settings=void 0,this.expansionCount=void 0,this.lexer=void 0,this.macros=void 0,this.stack=void 0,this.mode=void 0,this.settings=t,this.expansionCount=0,this.feed(e),this.macros=new Hn(On,t.macros),this.mode=r,this.stack=[]}var t=e.prototype;return t.feed=function(e){this.lexer=new Rn(e,this.settings)},t.switchMode=function(e){this.mode=e},t.beginGroup=function(){this.macros.beginGroup()},t.endGroup=function(){this.macros.endGroup()},t.endGroups=function(){this.macros.endGroups()},t.future=function(){return 0===this.stack.length&&this.pushToken(this.lexer.lex()),this.stack[this.stack.length-1]},t.popToken=function(){return this.future(),this.stack.pop()},t.pushToken=function(e){this.stack.push(e)},t.pushTokens=function(e){var t;(t=this.stack).push.apply(t,e)},t.scanArgument=function(e){var t,r,n;if(e){if(this.consumeSpaces(),"["!==this.future().text)return null;t=this.popToken();var a=this.consumeArg(["]"]);n=a.tokens,r=a.end}else{var i=this.consumeArg();n=i.tokens,t=i.start,r=i.end}return this.pushToken(new Gr("EOF",r.loc)),this.pushTokens(n),t.range(r,"")},t.consumeSpaces=function(){for(;;){if(" "!==this.future().text)break;this.stack.pop()}},t.consumeArg=function(e){var t=[],r=e&&e.length>0;r||this.consumeSpaces();var a,i=this.future(),o=0,s=0;do{if(a=this.popToken(),t.push(a),"{"===a.text)++o;else if("}"===a.text){if(-1===--o)throw new n("Extra }",a)}else if("EOF"===a.text)throw new n("Unexpected end of input in a macro argument, expected '"+(e&&r?e[s]:"}")+"'",a);if(e&&r)if((0===o||1===o&&"{"===e[s])&&a.text===e[s]){if(++s===e.length){t.splice(-s,s);break}}else s=0}while(0!==o||r);return"{"===i.text&&"}"===t[t.length-1].text&&(t.pop(),t.shift()),t.reverse(),{tokens:t,start:i,end:a}},t.consumeArgs=function(e,t){if(t){if(t.length!==e+1)throw new n("The length of delimiters doesn't match the number of args!");for(var r=t[0],a=0;athis.settings.maxExpand)throw new n("Too many expansions: infinite loop or need to increase maxExpand setting");var i=a.tokens,o=this.consumeArgs(a.numArgs,a.delimiters);if(a.numArgs)for(var s=(i=i.slice()).length-1;s>=0;--s){var l=i[s];if("#"===l.text){if(0===s)throw new n("Incomplete placeholder at end of macro body",l);if("#"===(l=i[--s]).text)i.splice(s+1,1);else{if(!/^[1-9]$/.test(l.text))throw new n("Not a valid argument number",l);var h;(h=i).splice.apply(h,[s,2].concat(o[+l.text-1]))}}}return this.pushTokens(i),i},t.expandAfterFuture=function(){return this.expandOnce(),this.future()},t.expandNextToken=function(){for(;;){var e=this.expandOnce();if(e instanceof Gr)return e.treatAsRelax&&(e.text="\\relax"),this.stack.pop()}throw new Error},t.expandMacro=function(e){return this.macros.has(e)?this.expandTokens([new Gr(e)]):void 0},t.expandTokens=function(e){var t=[],r=this.stack.length;for(this.pushTokens(e);this.stack.length>r;){var n=this.expandOnce(!0);n instanceof Gr&&(n.treatAsRelax&&(n.noexpand=!1,n.treatAsRelax=!1),t.push(this.stack.pop()))}return t},t.expandMacroAsText=function(e){var t=this.expandMacro(e);return t?t.map((function(e){return e.text})).join(""):t},t._getExpansion=function(e){var t=this.macros.get(e);if(null==t)return t;if(1===e.length){var r=this.lexer.catcodes[e];if(null!=r&&13!==r)return}var n="function"==typeof t?t(this):t;if("string"==typeof n){var a=0;if(-1!==n.indexOf("#"))for(var i=n.replace(/##/g,"");-1!==i.indexOf("#"+(a+1));)++a;for(var o=new Rn(n,this.settings),s=[],l=o.lex();"EOF"!==l.text;)s.push(l),l=o.lex();return s.reverse(),{tokens:s,numArgs:a}}return n},t.isDefined=function(e){return this.macros.has(e)||qn.hasOwnProperty(e)||ae.math.hasOwnProperty(e)||ae.text.hasOwnProperty(e)||Gn.hasOwnProperty(e)},t.isExpandable=function(e){var t=this.macros.get(e);return null!=t?"string"==typeof t||"function"==typeof t||!t.unexpandable:qn.hasOwnProperty(e)&&!qn[e].primitive},e}(),Yn=/^[\u208a\u208b\u208c\u208d\u208e\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089\u2090\u2091\u2095\u1d62\u2c7c\u2096\u2097\u2098\u2099\u2092\u209a\u1d63\u209b\u209c\u1d64\u1d65\u2093\u1d66\u1d67\u1d68\u1d69\u1d6a]/,Xn=Object.freeze({"\u208a":"+","\u208b":"-","\u208c":"=","\u208d":"(","\u208e":")","\u2080":"0","\u2081":"1","\u2082":"2","\u2083":"3","\u2084":"4","\u2085":"5","\u2086":"6","\u2087":"7","\u2088":"8","\u2089":"9","\u2090":"a","\u2091":"e","\u2095":"h","\u1d62":"i","\u2c7c":"j","\u2096":"k","\u2097":"l","\u2098":"m","\u2099":"n","\u2092":"o","\u209a":"p","\u1d63":"r","\u209b":"s","\u209c":"t","\u1d64":"u","\u1d65":"v","\u2093":"x","\u1d66":"\u03b2","\u1d67":"\u03b3","\u1d68":"\u03c1","\u1d69":"\u03d5","\u1d6a":"\u03c7","\u207a":"+","\u207b":"-","\u207c":"=","\u207d":"(","\u207e":")","\u2070":"0","\xb9":"1","\xb2":"2","\xb3":"3","\u2074":"4","\u2075":"5","\u2076":"6","\u2077":"7","\u2078":"8","\u2079":"9","\u1d2c":"A","\u1d2e":"B","\u1d30":"D","\u1d31":"E","\u1d33":"G","\u1d34":"H","\u1d35":"I","\u1d36":"J","\u1d37":"K","\u1d38":"L","\u1d39":"M","\u1d3a":"N","\u1d3c":"O","\u1d3e":"P","\u1d3f":"R","\u1d40":"T","\u1d41":"U","\u2c7d":"V","\u1d42":"W","\u1d43":"a","\u1d47":"b","\u1d9c":"c","\u1d48":"d","\u1d49":"e","\u1da0":"f","\u1d4d":"g","\u02b0":"h","\u2071":"i","\u02b2":"j","\u1d4f":"k","\u02e1":"l","\u1d50":"m","\u207f":"n","\u1d52":"o","\u1d56":"p","\u02b3":"r","\u02e2":"s","\u1d57":"t","\u1d58":"u","\u1d5b":"v","\u02b7":"w","\u02e3":"x","\u02b8":"y","\u1dbb":"z","\u1d5d":"\u03b2","\u1d5e":"\u03b3","\u1d5f":"\u03b4","\u1d60":"\u03d5","\u1d61":"\u03c7","\u1dbf":"\u03b8"}),Wn={"\u0301":{text:"\\'",math:"\\acute"},"\u0300":{text:"\\`",math:"\\grave"},"\u0308":{text:'\\"',math:"\\ddot"},"\u0303":{text:"\\~",math:"\\tilde"},"\u0304":{text:"\\=",math:"\\bar"},"\u0306":{text:"\\u",math:"\\breve"},"\u030c":{text:"\\v",math:"\\check"},"\u0302":{text:"\\^",math:"\\hat"},"\u0307":{text:"\\.",math:"\\dot"},"\u030a":{text:"\\r",math:"\\mathring"},"\u030b":{text:"\\H"},"\u0327":{text:"\\c"}},_n={"\xe1":"a\u0301","\xe0":"a\u0300","\xe4":"a\u0308","\u01df":"a\u0308\u0304","\xe3":"a\u0303","\u0101":"a\u0304","\u0103":"a\u0306","\u1eaf":"a\u0306\u0301","\u1eb1":"a\u0306\u0300","\u1eb5":"a\u0306\u0303","\u01ce":"a\u030c","\xe2":"a\u0302","\u1ea5":"a\u0302\u0301","\u1ea7":"a\u0302\u0300","\u1eab":"a\u0302\u0303","\u0227":"a\u0307","\u01e1":"a\u0307\u0304","\xe5":"a\u030a","\u01fb":"a\u030a\u0301","\u1e03":"b\u0307","\u0107":"c\u0301","\u1e09":"c\u0327\u0301","\u010d":"c\u030c","\u0109":"c\u0302","\u010b":"c\u0307","\xe7":"c\u0327","\u010f":"d\u030c","\u1e0b":"d\u0307","\u1e11":"d\u0327","\xe9":"e\u0301","\xe8":"e\u0300","\xeb":"e\u0308","\u1ebd":"e\u0303","\u0113":"e\u0304","\u1e17":"e\u0304\u0301","\u1e15":"e\u0304\u0300","\u0115":"e\u0306","\u1e1d":"e\u0327\u0306","\u011b":"e\u030c","\xea":"e\u0302","\u1ebf":"e\u0302\u0301","\u1ec1":"e\u0302\u0300","\u1ec5":"e\u0302\u0303","\u0117":"e\u0307","\u0229":"e\u0327","\u1e1f":"f\u0307","\u01f5":"g\u0301","\u1e21":"g\u0304","\u011f":"g\u0306","\u01e7":"g\u030c","\u011d":"g\u0302","\u0121":"g\u0307","\u0123":"g\u0327","\u1e27":"h\u0308","\u021f":"h\u030c","\u0125":"h\u0302","\u1e23":"h\u0307","\u1e29":"h\u0327","\xed":"i\u0301","\xec":"i\u0300","\xef":"i\u0308","\u1e2f":"i\u0308\u0301","\u0129":"i\u0303","\u012b":"i\u0304","\u012d":"i\u0306","\u01d0":"i\u030c","\xee":"i\u0302","\u01f0":"j\u030c","\u0135":"j\u0302","\u1e31":"k\u0301","\u01e9":"k\u030c","\u0137":"k\u0327","\u013a":"l\u0301","\u013e":"l\u030c","\u013c":"l\u0327","\u1e3f":"m\u0301","\u1e41":"m\u0307","\u0144":"n\u0301","\u01f9":"n\u0300","\xf1":"n\u0303","\u0148":"n\u030c","\u1e45":"n\u0307","\u0146":"n\u0327","\xf3":"o\u0301","\xf2":"o\u0300","\xf6":"o\u0308","\u022b":"o\u0308\u0304","\xf5":"o\u0303","\u1e4d":"o\u0303\u0301","\u1e4f":"o\u0303\u0308","\u022d":"o\u0303\u0304","\u014d":"o\u0304","\u1e53":"o\u0304\u0301","\u1e51":"o\u0304\u0300","\u014f":"o\u0306","\u01d2":"o\u030c","\xf4":"o\u0302","\u1ed1":"o\u0302\u0301","\u1ed3":"o\u0302\u0300","\u1ed7":"o\u0302\u0303","\u022f":"o\u0307","\u0231":"o\u0307\u0304","\u0151":"o\u030b","\u1e55":"p\u0301","\u1e57":"p\u0307","\u0155":"r\u0301","\u0159":"r\u030c","\u1e59":"r\u0307","\u0157":"r\u0327","\u015b":"s\u0301","\u1e65":"s\u0301\u0307","\u0161":"s\u030c","\u1e67":"s\u030c\u0307","\u015d":"s\u0302","\u1e61":"s\u0307","\u015f":"s\u0327","\u1e97":"t\u0308","\u0165":"t\u030c","\u1e6b":"t\u0307","\u0163":"t\u0327","\xfa":"u\u0301","\xf9":"u\u0300","\xfc":"u\u0308","\u01d8":"u\u0308\u0301","\u01dc":"u\u0308\u0300","\u01d6":"u\u0308\u0304","\u01da":"u\u0308\u030c","\u0169":"u\u0303","\u1e79":"u\u0303\u0301","\u016b":"u\u0304","\u1e7b":"u\u0304\u0308","\u016d":"u\u0306","\u01d4":"u\u030c","\xfb":"u\u0302","\u016f":"u\u030a","\u0171":"u\u030b","\u1e7d":"v\u0303","\u1e83":"w\u0301","\u1e81":"w\u0300","\u1e85":"w\u0308","\u0175":"w\u0302","\u1e87":"w\u0307","\u1e98":"w\u030a","\u1e8d":"x\u0308","\u1e8b":"x\u0307","\xfd":"y\u0301","\u1ef3":"y\u0300","\xff":"y\u0308","\u1ef9":"y\u0303","\u0233":"y\u0304","\u0177":"y\u0302","\u1e8f":"y\u0307","\u1e99":"y\u030a","\u017a":"z\u0301","\u017e":"z\u030c","\u1e91":"z\u0302","\u017c":"z\u0307","\xc1":"A\u0301","\xc0":"A\u0300","\xc4":"A\u0308","\u01de":"A\u0308\u0304","\xc3":"A\u0303","\u0100":"A\u0304","\u0102":"A\u0306","\u1eae":"A\u0306\u0301","\u1eb0":"A\u0306\u0300","\u1eb4":"A\u0306\u0303","\u01cd":"A\u030c","\xc2":"A\u0302","\u1ea4":"A\u0302\u0301","\u1ea6":"A\u0302\u0300","\u1eaa":"A\u0302\u0303","\u0226":"A\u0307","\u01e0":"A\u0307\u0304","\xc5":"A\u030a","\u01fa":"A\u030a\u0301","\u1e02":"B\u0307","\u0106":"C\u0301","\u1e08":"C\u0327\u0301","\u010c":"C\u030c","\u0108":"C\u0302","\u010a":"C\u0307","\xc7":"C\u0327","\u010e":"D\u030c","\u1e0a":"D\u0307","\u1e10":"D\u0327","\xc9":"E\u0301","\xc8":"E\u0300","\xcb":"E\u0308","\u1ebc":"E\u0303","\u0112":"E\u0304","\u1e16":"E\u0304\u0301","\u1e14":"E\u0304\u0300","\u0114":"E\u0306","\u1e1c":"E\u0327\u0306","\u011a":"E\u030c","\xca":"E\u0302","\u1ebe":"E\u0302\u0301","\u1ec0":"E\u0302\u0300","\u1ec4":"E\u0302\u0303","\u0116":"E\u0307","\u0228":"E\u0327","\u1e1e":"F\u0307","\u01f4":"G\u0301","\u1e20":"G\u0304","\u011e":"G\u0306","\u01e6":"G\u030c","\u011c":"G\u0302","\u0120":"G\u0307","\u0122":"G\u0327","\u1e26":"H\u0308","\u021e":"H\u030c","\u0124":"H\u0302","\u1e22":"H\u0307","\u1e28":"H\u0327","\xcd":"I\u0301","\xcc":"I\u0300","\xcf":"I\u0308","\u1e2e":"I\u0308\u0301","\u0128":"I\u0303","\u012a":"I\u0304","\u012c":"I\u0306","\u01cf":"I\u030c","\xce":"I\u0302","\u0130":"I\u0307","\u0134":"J\u0302","\u1e30":"K\u0301","\u01e8":"K\u030c","\u0136":"K\u0327","\u0139":"L\u0301","\u013d":"L\u030c","\u013b":"L\u0327","\u1e3e":"M\u0301","\u1e40":"M\u0307","\u0143":"N\u0301","\u01f8":"N\u0300","\xd1":"N\u0303","\u0147":"N\u030c","\u1e44":"N\u0307","\u0145":"N\u0327","\xd3":"O\u0301","\xd2":"O\u0300","\xd6":"O\u0308","\u022a":"O\u0308\u0304","\xd5":"O\u0303","\u1e4c":"O\u0303\u0301","\u1e4e":"O\u0303\u0308","\u022c":"O\u0303\u0304","\u014c":"O\u0304","\u1e52":"O\u0304\u0301","\u1e50":"O\u0304\u0300","\u014e":"O\u0306","\u01d1":"O\u030c","\xd4":"O\u0302","\u1ed0":"O\u0302\u0301","\u1ed2":"O\u0302\u0300","\u1ed6":"O\u0302\u0303","\u022e":"O\u0307","\u0230":"O\u0307\u0304","\u0150":"O\u030b","\u1e54":"P\u0301","\u1e56":"P\u0307","\u0154":"R\u0301","\u0158":"R\u030c","\u1e58":"R\u0307","\u0156":"R\u0327","\u015a":"S\u0301","\u1e64":"S\u0301\u0307","\u0160":"S\u030c","\u1e66":"S\u030c\u0307","\u015c":"S\u0302","\u1e60":"S\u0307","\u015e":"S\u0327","\u0164":"T\u030c","\u1e6a":"T\u0307","\u0162":"T\u0327","\xda":"U\u0301","\xd9":"U\u0300","\xdc":"U\u0308","\u01d7":"U\u0308\u0301","\u01db":"U\u0308\u0300","\u01d5":"U\u0308\u0304","\u01d9":"U\u0308\u030c","\u0168":"U\u0303","\u1e78":"U\u0303\u0301","\u016a":"U\u0304","\u1e7a":"U\u0304\u0308","\u016c":"U\u0306","\u01d3":"U\u030c","\xdb":"U\u0302","\u016e":"U\u030a","\u0170":"U\u030b","\u1e7c":"V\u0303","\u1e82":"W\u0301","\u1e80":"W\u0300","\u1e84":"W\u0308","\u0174":"W\u0302","\u1e86":"W\u0307","\u1e8c":"X\u0308","\u1e8a":"X\u0307","\xdd":"Y\u0301","\u1ef2":"Y\u0300","\u0178":"Y\u0308","\u1ef8":"Y\u0303","\u0232":"Y\u0304","\u0176":"Y\u0302","\u1e8e":"Y\u0307","\u0179":"Z\u0301","\u017d":"Z\u030c","\u1e90":"Z\u0302","\u017b":"Z\u0307","\u03ac":"\u03b1\u0301","\u1f70":"\u03b1\u0300","\u1fb1":"\u03b1\u0304","\u1fb0":"\u03b1\u0306","\u03ad":"\u03b5\u0301","\u1f72":"\u03b5\u0300","\u03ae":"\u03b7\u0301","\u1f74":"\u03b7\u0300","\u03af":"\u03b9\u0301","\u1f76":"\u03b9\u0300","\u03ca":"\u03b9\u0308","\u0390":"\u03b9\u0308\u0301","\u1fd2":"\u03b9\u0308\u0300","\u1fd1":"\u03b9\u0304","\u1fd0":"\u03b9\u0306","\u03cc":"\u03bf\u0301","\u1f78":"\u03bf\u0300","\u03cd":"\u03c5\u0301","\u1f7a":"\u03c5\u0300","\u03cb":"\u03c5\u0308","\u03b0":"\u03c5\u0308\u0301","\u1fe2":"\u03c5\u0308\u0300","\u1fe1":"\u03c5\u0304","\u1fe0":"\u03c5\u0306","\u03ce":"\u03c9\u0301","\u1f7c":"\u03c9\u0300","\u038e":"\u03a5\u0301","\u1fea":"\u03a5\u0300","\u03ab":"\u03a5\u0308","\u1fe9":"\u03a5\u0304","\u1fe8":"\u03a5\u0306","\u038f":"\u03a9\u0301","\u1ffa":"\u03a9\u0300"},jn=function(){function e(e,t){this.mode=void 0,this.gullet=void 0,this.settings=void 0,this.leftrightDepth=void 0,this.nextToken=void 0,this.mode="math",this.gullet=new Un(e,t,this.mode),this.settings=t,this.leftrightDepth=0}var t=e.prototype;return t.expect=function(e,t){if(void 0===t&&(t=!0),this.fetch().text!==e)throw new n("Expected '"+e+"', got '"+this.fetch().text+"'",this.fetch());t&&this.consume()},t.consume=function(){this.nextToken=null},t.fetch=function(){return null==this.nextToken&&(this.nextToken=this.gullet.expandNextToken()),this.nextToken},t.switchMode=function(e){this.mode=e,this.gullet.switchMode(e)},t.parse=function(){this.settings.globalGroup||this.gullet.beginGroup(),this.settings.colorIsTextColor&&this.gullet.macros.set("\\color","\\textcolor");try{var e=this.parseExpression(!1);return this.expect("EOF"),this.settings.globalGroup||this.gullet.endGroup(),e}finally{this.gullet.endGroups()}},t.subparse=function(e){var t=this.nextToken;this.consume(),this.gullet.pushToken(new Gr("}")),this.gullet.pushTokens(e);var r=this.parseExpression(!1);return this.expect("}"),this.nextToken=t,r},t.parseExpression=function(t,r){for(var n=[];;){"math"===this.mode&&this.consumeSpaces();var a=this.fetch();if(-1!==e.endOfExpression.indexOf(a.text))break;if(r&&a.text===r)break;if(t&&qn[a.text]&&qn[a.text].infix)break;var i=this.parseAtom(r);if(!i)break;"internal"!==i.type&&n.push(i)}return"text"===this.mode&&this.formLigatures(n),this.handleInfixNodes(n)},t.handleInfixNodes=function(e){for(var t,r=-1,a=0;a=0&&this.settings.reportNonstrict("unicodeTextInMathMode",'Latin-1/Unicode text character "'+t[0]+'" used in math mode',e);var s,l=ae[this.mode][t].group,h=Fr.range(e);if(te.hasOwnProperty(l)){var c=l;s={type:"atom",mode:this.mode,family:c,loc:h,text:t}}else s={type:l,mode:this.mode,loc:h,text:t};i=s}else{if(!(t.charCodeAt(0)>=128))return null;this.settings.strict&&(S(t.charCodeAt(0))?"math"===this.mode&&this.settings.reportNonstrict("unicodeTextInMathMode",'Unicode text character "'+t[0]+'" used in math mode',e):this.settings.reportNonstrict("unknownSymbol",'Unrecognized Unicode character "'+t[0]+'" ('+t.charCodeAt(0)+")",e)),i={type:"textord",mode:"text",loc:Fr.range(e),text:t}}if(this.consume(),o)for(var m=0;m=0;i--)t[i].loc.start>o&&(n+=" ",o=t[i].loc.start),n+=t[i].text,o+=t[i].text.length;return r.go(a.go(n,e))},a={go:function(t,e){if(!t)return[];void 0===e&&(e="ce");var n,o="0",r={};r.parenthesisLevel=0,t=(t=(t=t.replace(/\n/g," ")).replace(/[\u2212\u2013\u2014\u2010]/g,"-")).replace(/[\u2026]/g,"...");for(var i=10,c=[];;){n!==t?(i=10,n=t):i--;var u=a.stateMachines[e],p=u.transitions[o]||u.transitions["*"];t:for(var s=0;s0))return c;if(d.revisit||(t=_.remainder),!d.toContinue)break t}}if(i<=0)throw["MhchemBugU","mhchem bug U. Please report."]}},concatArray:function(t,e){if(e)if(Array.isArray(e))for(var n=0;n":/^[=<>]/,"#":/^[#\u2261]/,"+":/^\+/,"-$":/^-(?=[\s_},;\]/]|$|\([a-z]+\))/,"-9":/^-(?=[0-9])/,"- orbital overlap":/^-(?=(?:[spd]|sp)(?:$|[\s,;\)\]\}]))/,"-":/^-/,"pm-operator":/^(?:\\pm|\$\\pm\$|\+-|\+\/-)/,operator:/^(?:\+|(?:[\-=<>]|<<|>>|\\approx|\$\\approx\$)(?=\s|$|-?[0-9]))/,arrowUpDown:/^(?:v|\(v\)|\^|\(\^\))(?=$|[\s,;\)\]\}])/,"\\bond{(...)}":function(t){return a.patterns.findObserveGroups(t,"\\bond{","","","}")},"->":/^(?:<->|<-->|->|<-|<=>>|<<=>|<=>|[\u2192\u27F6\u21CC])/,CMT:/^[CMT](?=\[)/,"[(...)]":function(t){return a.patterns.findObserveGroups(t,"[","","","]")},"1st-level escape":/^(&|\\\\|\\hline)\s*/,"\\,":/^(?:\\[,\ ;:])/,"\\x{}{}":function(t){return a.patterns.findObserveGroups(t,"",/^\\[a-zA-Z]+\{/,"}","","","{","}","",!0)},"\\x{}":function(t){return a.patterns.findObserveGroups(t,"",/^\\[a-zA-Z]+\{/,"}","")},"\\ca":/^\\ca(?:\s+|(?![a-zA-Z]))/,"\\x":/^(?:\\[a-zA-Z]+\s*|\\[_&{}%])/,orbital:/^(?:[0-9]{1,2}[spdfgh]|[0-9]{0,2}sp)(?=$|[^a-zA-Z])/,others:/^[\/~|]/,"\\frac{(...)}":function(t){return a.patterns.findObserveGroups(t,"\\frac{","","","}","{","","","}")},"\\overset{(...)}":function(t){return a.patterns.findObserveGroups(t,"\\overset{","","","}","{","","","}")},"\\underset{(...)}":function(t){return a.patterns.findObserveGroups(t,"\\underset{","","","}","{","","","}")},"\\underbrace{(...)}":function(t){return a.patterns.findObserveGroups(t,"\\underbrace{","","","}_","{","","","}")},"\\color{(...)}0":function(t){return a.patterns.findObserveGroups(t,"\\color{","","","}")},"\\color{(...)}{(...)}1":function(t){return a.patterns.findObserveGroups(t,"\\color{","","","}","{","","","}")},"\\color(...){(...)}2":function(t){return a.patterns.findObserveGroups(t,"\\color","\\","",/^(?=\{)/,"{","","","}")},"\\ce{(...)}":function(t){return a.patterns.findObserveGroups(t,"\\ce{","","","}")},oxidation$:/^(?:[+-][IVX]+|\\pm\s*0|\$\\pm\$\s*0)$/,"d-oxidation$":/^(?:[+-]?\s?[IVX]+|\\pm\s*0|\$\\pm\$\s*0)$/,"roman numeral":/^[IVX]+/,"1/2$":/^[+\-]?(?:[0-9]+|\$[a-z]\$|[a-z])\/[0-9]+(?:\$[a-z]\$|[a-z])?$/,amount:function(t){var e;if(e=t.match(/^(?:(?:(?:\([+\-]?[0-9]+\/[0-9]+\)|[+\-]?(?:[0-9]+|\$[a-z]\$|[a-z])\/[0-9]+|[+\-]?[0-9]+[.,][0-9]+|[+\-]?\.[0-9]+|[+\-]?[0-9]+)(?:[a-z](?=\s*[A-Z]))?)|[+\-]?[a-z](?=\s*[A-Z])|\+(?!\s))/))return{match_:e[0],remainder:t.substr(e[0].length)};var n=a.patterns.findObserveGroups(t,"","$","$","");return n&&(e=n.match_.match(/^\$(?:\(?[+\-]?(?:[0-9]*[a-z]?[+\-])?[0-9]*[a-z](?:[+\-][0-9]*[a-z]?)?\)?|\+|-)\$$/))?{match_:e[0],remainder:t.substr(e[0].length)}:null},amount2:function(t){return this.amount(t)},"(KV letters),":/^(?:[A-Z][a-z]{0,2}|i)(?=,)/,formula$:function(t){if(t.match(/^\([a-z]+\)$/))return null;var e=t.match(/^(?:[a-z]|(?:[0-9\ \+\-\,\.\(\)]+[a-z])+[0-9\ \+\-\,\.\(\)]*|(?:[a-z][0-9\ \+\-\,\.\(\)]+)+[a-z]?)$/);return e?{match_:e[0],remainder:t.substr(e[0].length)}:null},uprightEntities:/^(?:pH|pOH|pC|pK|iPr|iBu)(?=$|[^a-zA-Z])/,"/":/^\s*(\/)\s*/,"//":/^\s*(\/\/)\s*/,"*":/^\s*[*.]\s*/},findObserveGroups:function(t,e,n,o,a,r,i,c,u,p){var s=function(t,e){if("string"==typeof e)return 0!==t.indexOf(e)?null:e;var n=t.match(e);return n?n[0]:null},_=s(t,e);if(null===_)return null;if(t=t.substr(_.length),null===(_=s(t,n)))return null;var d=function(t,e,n){for(var o=0;e":{"0|1|2|3":{action_:"r=",nextState:"r"},"a|as":{action_:["output","r="],nextState:"r"},"*":{action_:["output","r="],nextState:"r"}},"+":{o:{action_:"d= kv",nextState:"d"},"d|D":{action_:"d=",nextState:"d"},q:{action_:"d=",nextState:"qd"},"qd|qD":{action_:"d=",nextState:"qd"},dq:{action_:["output","d="],nextState:"d"},3:{action_:["sb=false","output","operator"],nextState:"0"}},amount:{"0|2":{action_:"a=",nextState:"a"}},"pm-operator":{"0|1|2|a|as":{action_:["sb=false","output",{type_:"operator",option:"\\pm"}],nextState:"0"}},operator:{"0|1|2|a|as":{action_:["sb=false","output","operator"],nextState:"0"}},"-$":{"o|q":{action_:["charge or bond","output"],nextState:"qd"},d:{action_:"d=",nextState:"d"},D:{action_:["output",{type_:"bond",option:"-"}],nextState:"3"},q:{action_:"d=",nextState:"qd"},qd:{action_:"d=",nextState:"qd"},"qD|dq":{action_:["output",{type_:"bond",option:"-"}],nextState:"3"}},"-9":{"3|o":{action_:["output",{type_:"insert",option:"hyphen"}],nextState:"3"}},"- orbital overlap":{o:{action_:["output",{type_:"insert",option:"hyphen"}],nextState:"2"},d:{action_:["output",{type_:"insert",option:"hyphen"}],nextState:"2"}},"-":{"0|1|2":{action_:[{type_:"output",option:1},"beginsWithBond=true",{type_:"bond",option:"-"}],nextState:"3"},3:{action_:{type_:"bond",option:"-"}},a:{action_:["output",{type_:"insert",option:"hyphen"}],nextState:"2"},as:{action_:[{type_:"output",option:2},{type_:"bond",option:"-"}],nextState:"3"},b:{action_:"b="},o:{action_:{type_:"- after o/d",option:!1},nextState:"2"},q:{action_:{type_:"- after o/d",option:!1},nextState:"2"},"d|qd|dq":{action_:{type_:"- after o/d",option:!0},nextState:"2"},"D|qD|p":{action_:["output",{type_:"bond",option:"-"}],nextState:"3"}},amount2:{"1|3":{action_:"a=",nextState:"a"}},letters:{"0|1|2|3|a|as|b|p|bp|o":{action_:"o=",nextState:"o"},"q|dq":{action_:["output","o="],nextState:"o"},"d|D|qd|qD":{action_:"o after d",nextState:"o"}},digits:{o:{action_:"q=",nextState:"q"},"d|D":{action_:"q=",nextState:"dq"},q:{action_:["output","o="],nextState:"o"},a:{action_:"o=",nextState:"o"}},"space A":{"b|p|bp":{}},space:{a:{nextState:"as"},0:{action_:"sb=false"},"1|2":{action_:"sb=true"},"r|rt|rd|rdt|rdq":{action_:"output",nextState:"0"},"*":{action_:["output","sb=true"],nextState:"1"}},"1st-level escape":{"1|2":{action_:["output",{type_:"insert+p1",option:"1st-level escape"}]},"*":{action_:["output",{type_:"insert+p1",option:"1st-level escape"}],nextState:"0"}},"[(...)]":{"r|rt":{action_:"rd=",nextState:"rd"},"rd|rdt":{action_:"rq=",nextState:"rdq"}},"...":{"o|d|D|dq|qd|qD":{action_:["output",{type_:"bond",option:"..."}],nextState:"3"},"*":{action_:[{type_:"output",option:1},{type_:"insert",option:"ellipsis"}],nextState:"1"}},". |* ":{"*":{action_:["output",{type_:"insert",option:"addition compound"}],nextState:"1"}},"state of aggregation $":{"*":{action_:["output","state of aggregation"],nextState:"1"}},"{[(":{"a|as|o":{action_:["o=","output","parenthesisLevel++"],nextState:"2"},"0|1|2|3":{action_:["o=","output","parenthesisLevel++"],nextState:"2"},"*":{action_:["output","o=","output","parenthesisLevel++"],nextState:"2"}},")]}":{"0|1|2|3|b|p|bp|o":{action_:["o=","parenthesisLevel--"],nextState:"o"},"a|as|d|D|q|qd|qD|dq":{action_:["output","o=","parenthesisLevel--"],nextState:"o"}},", ":{"*":{action_:["output","comma"],nextState:"0"}},"^_":{"*":{}},"^{(...)}|^($...$)":{"0|1|2|as":{action_:"b=",nextState:"b"},p:{action_:"b=",nextState:"bp"},"3|o":{action_:"d= kv",nextState:"D"},q:{action_:"d=",nextState:"qD"},"d|D|qd|qD|dq":{action_:["output","d="],nextState:"D"}},"^a|^\\x{}{}|^\\x{}|^\\x|'":{"0|1|2|as":{action_:"b=",nextState:"b"},p:{action_:"b=",nextState:"bp"},"3|o":{action_:"d= kv",nextState:"d"},q:{action_:"d=",nextState:"qd"},"d|qd|D|qD":{action_:"d="},dq:{action_:["output","d="],nextState:"d"}},"_{(state of aggregation)}$":{"d|D|q|qd|qD|dq":{action_:["output","q="],nextState:"q"}},"_{(...)}|_($...$)|_9|_\\x{}{}|_\\x{}|_\\x":{"0|1|2|as":{action_:"p=",nextState:"p"},b:{action_:"p=",nextState:"bp"},"3|o":{action_:"q=",nextState:"q"},"d|D":{action_:"q=",nextState:"dq"},"q|qd|qD|dq":{action_:["output","q="],nextState:"q"}},"=<>":{"0|1|2|3|a|as|o|q|d|D|qd|qD|dq":{action_:[{type_:"output",option:2},"bond"],nextState:"3"}},"#":{"0|1|2|3|a|as|o":{action_:[{type_:"output",option:2},{type_:"bond",option:"#"}],nextState:"3"}},"{}":{"*":{action_:{type_:"output",option:1},nextState:"1"}},"{...}":{"0|1|2|3|a|as|b|p|bp":{action_:"o=",nextState:"o"},"o|d|D|q|qd|qD|dq":{action_:["output","o="],nextState:"o"}},"$...$":{a:{action_:"a="},"0|1|2|3|as|b|p|bp|o":{action_:"o=",nextState:"o"},"as|o":{action_:"o="},"q|d|D|qd|qD|dq":{action_:["output","o="],nextState:"o"}},"\\bond{(...)}":{"*":{action_:[{type_:"output",option:2},"bond"],nextState:"3"}},"\\frac{(...)}":{"*":{action_:[{type_:"output",option:1},"frac-output"],nextState:"3"}},"\\overset{(...)}":{"*":{action_:[{type_:"output",option:2},"overset-output"],nextState:"3"}},"\\underset{(...)}":{"*":{action_:[{type_:"output",option:2},"underset-output"],nextState:"3"}},"\\underbrace{(...)}":{"*":{action_:[{type_:"output",option:2},"underbrace-output"],nextState:"3"}},"\\color{(...)}{(...)}1|\\color(...){(...)}2":{"*":{action_:[{type_:"output",option:2},"color-output"],nextState:"3"}},"\\color{(...)}0":{"*":{action_:[{type_:"output",option:2},"color0-output"]}},"\\ce{(...)}":{"*":{action_:[{type_:"output",option:2},"ce"],nextState:"3"}},"\\,":{"*":{action_:[{type_:"output",option:1},"copy"],nextState:"1"}},"\\x{}{}|\\x{}|\\x":{"0|1|2|3|a|as|b|p|bp|o|c0":{action_:["o=","output"],nextState:"3"},"*":{action_:["output","o=","output"],nextState:"3"}},others:{"*":{action_:[{type_:"output",option:1},"copy"],nextState:"3"}},else2:{a:{action_:"a to o",nextState:"o",revisit:!0},as:{action_:["output","sb=true"],nextState:"1",revisit:!0},"r|rt|rd|rdt|rdq":{action_:["output"],nextState:"0",revisit:!0},"*":{action_:["output","copy"],nextState:"3"}}}),actions:{"o after d":function(t,e){var n;if((t.d||"").match(/^[0-9]+$/)){var o=t.d;t.d=void 0,n=this.output(t),t.b=o}else n=this.output(t);return a.actions["o="](t,e),n},"d= kv":function(t,e){t.d=e,t.dType="kv"},"charge or bond":function(t,e){if(t.beginsWithBond){var n=[];return a.concatArray(n,this.output(t)),a.concatArray(n,a.actions.bond(t,e,"-")),n}t.d=e},"- after o/d":function(t,e,n){var o=a.patterns.match_("orbital",t.o||""),r=a.patterns.match_("one lowercase greek letter $",t.o||""),i=a.patterns.match_("one lowercase latin letter $",t.o||""),c=a.patterns.match_("$one lowercase latin letter$ $",t.o||""),u="-"===e&&(o&&""===o.remainder||r||i||c);!u||t.a||t.b||t.p||t.d||t.q||o||!i||(t.o="$"+t.o+"$");var p=[];return u?(a.concatArray(p,this.output(t)),p.push({type_:"hyphen"})):(o=a.patterns.match_("digits",t.d||""),n&&o&&""===o.remainder?(a.concatArray(p,a.actions["d="](t,e)),a.concatArray(p,this.output(t))):(a.concatArray(p,this.output(t)),a.concatArray(p,a.actions.bond(t,e,"-")))),p},"a to o":function(t){t.o=t.a,t.a=void 0},"sb=true":function(t){t.sb=!0},"sb=false":function(t){t.sb=!1},"beginsWithBond=true":function(t){t.beginsWithBond=!0},"beginsWithBond=false":function(t){t.beginsWithBond=!1},"parenthesisLevel++":function(t){t.parenthesisLevel++},"parenthesisLevel--":function(t){t.parenthesisLevel--},"state of aggregation":function(t,e){return{type_:"state of aggregation",p1:a.go(e,"o")}},comma:function(t,e){var n=e.replace(/\s*$/,"");return n!==e&&0===t.parenthesisLevel?{type_:"comma enumeration L",p1:n}:{type_:"comma enumeration M",p1:n}},output:function(t,e,n){var o,r,i;t.r?(r="M"===t.rdt?a.go(t.rd,"tex-math"):"T"===t.rdt?[{type_:"text",p1:t.rd||""}]:a.go(t.rd),i="M"===t.rqt?a.go(t.rq,"tex-math"):"T"===t.rqt?[{type_:"text",p1:t.rq||""}]:a.go(t.rq),o={type_:"arrow",r:t.r,rd:r,rq:i}):(o=[],(t.a||t.b||t.p||t.o||t.q||t.d||n)&&(t.sb&&o.push({type_:"entitySkip"}),t.o||t.q||t.d||t.b||t.p||2===n?t.o||t.q||t.d||!t.b&&!t.p?t.o&&"kv"===t.dType&&a.patterns.match_("d-oxidation$",t.d||"")?t.dType="oxidation":t.o&&"kv"===t.dType&&!t.q&&(t.dType=void 0):(t.o=t.a,t.d=t.b,t.q=t.p,t.a=t.b=t.p=void 0):(t.o=t.a,t.a=void 0),o.push({type_:"chemfive",a:a.go(t.a,"a"),b:a.go(t.b,"bd"),p:a.go(t.p,"pq"),o:a.go(t.o,"o"),q:a.go(t.q,"pq"),d:a.go(t.d,"oxidation"===t.dType?"oxidation":"bd"),dType:t.dType})));for(var c in t)"parenthesisLevel"!==c&&"beginsWithBond"!==c&&delete t[c];return o},"oxidation-output":function(t,e){var n=["{"];return a.concatArray(n,a.go(e,"oxidation")),n.push("}"),n},"frac-output":function(t,e){return{type_:"frac-ce",p1:a.go(e[0]),p2:a.go(e[1])}},"overset-output":function(t,e){return{type_:"overset",p1:a.go(e[0]),p2:a.go(e[1])}},"underset-output":function(t,e){return{type_:"underset",p1:a.go(e[0]),p2:a.go(e[1])}},"underbrace-output":function(t,e){return{type_:"underbrace",p1:a.go(e[0]),p2:a.go(e[1])}},"color-output":function(t,e){return{type_:"color",color1:e[0],color2:a.go(e[1])}},"r=":function(t,e){t.r=e},"rdt=":function(t,e){t.rdt=e},"rd=":function(t,e){t.rd=e},"rqt=":function(t,e){t.rqt=e},"rq=":function(t,e){t.rq=e},operator:function(t,e,n){return{type_:"operator",kind_:n||e}}}},a:{transitions:a.createTransitions({empty:{"*":{}},"1/2$":{0:{action_:"1/2"}},else:{0:{nextState:"1",revisit:!0}},"$(...)$":{"*":{action_:"tex-math tight",nextState:"1"}},",":{"*":{action_:{type_:"insert",option:"commaDecimal"}}},else2:{"*":{action_:"copy"}}}),actions:{}},o:{transitions:a.createTransitions({empty:{"*":{}},"1/2$":{0:{action_:"1/2"}},else:{0:{nextState:"1",revisit:!0}},letters:{"*":{action_:"rm"}},"\\ca":{"*":{action_:{type_:"insert",option:"circa"}}},"\\x{}{}|\\x{}|\\x":{"*":{action_:"copy"}},"${(...)}$|$(...)$":{"*":{action_:"tex-math"}},"{(...)}":{"*":{action_:"{text}"}},else2:{"*":{action_:"copy"}}}),actions:{}},text:{transitions:a.createTransitions({empty:{"*":{action_:"output"}},"{...}":{"*":{action_:"text="}},"${(...)}$|$(...)$":{"*":{action_:"tex-math"}},"\\greek":{"*":{action_:["output","rm"]}},"\\,|\\x{}{}|\\x{}|\\x":{"*":{action_:["output","copy"]}},else:{"*":{action_:"text="}}}),actions:{output:function(t){if(t.text_){var e={type_:"text",p1:t.text_};for(var n in t)delete t[n];return e}}}},pq:{transitions:a.createTransitions({empty:{"*":{}},"state of aggregation $":{"*":{action_:"state of aggregation"}},i$:{0:{nextState:"!f",revisit:!0}},"(KV letters),":{0:{action_:"rm",nextState:"0"}},formula$:{0:{nextState:"f",revisit:!0}},"1/2$":{0:{action_:"1/2"}},else:{0:{nextState:"!f",revisit:!0}},"${(...)}$|$(...)$":{"*":{action_:"tex-math"}},"{(...)}":{"*":{action_:"text"}},"a-z":{f:{action_:"tex-math"}},letters:{"*":{action_:"rm"}},"-9.,9":{"*":{action_:"9,9"}},",":{"*":{action_:{type_:"insert+p1",option:"comma enumeration S"}}},"\\color{(...)}{(...)}1|\\color(...){(...)}2":{"*":{action_:"color-output"}},"\\color{(...)}0":{"*":{action_:"color0-output"}},"\\ce{(...)}":{"*":{action_:"ce"}},"\\,|\\x{}{}|\\x{}|\\x":{"*":{action_:"copy"}},else2:{"*":{action_:"copy"}}}),actions:{"state of aggregation":function(t,e){return{type_:"state of aggregation subscript",p1:a.go(e,"o")}},"color-output":function(t,e){return{type_:"color",color1:e[0],color2:a.go(e[1],"pq")}}}},bd:{transitions:a.createTransitions({empty:{"*":{}},x$:{0:{nextState:"!f",revisit:!0}},formula$:{0:{nextState:"f",revisit:!0}},else:{0:{nextState:"!f",revisit:!0}},"-9.,9 no missing 0":{"*":{action_:"9,9"}},".":{"*":{action_:{type_:"insert",option:"electron dot"}}},"a-z":{f:{action_:"tex-math"}},x:{"*":{action_:{type_:"insert",option:"KV x"}}},letters:{"*":{action_:"rm"}},"'":{"*":{action_:{type_:"insert",option:"prime"}}},"${(...)}$|$(...)$":{"*":{action_:"tex-math"}},"{(...)}":{"*":{action_:"text"}},"\\color{(...)}{(...)}1|\\color(...){(...)}2":{"*":{action_:"color-output"}},"\\color{(...)}0":{"*":{action_:"color0-output"}},"\\ce{(...)}":{"*":{action_:"ce"}},"\\,|\\x{}{}|\\x{}|\\x":{"*":{action_:"copy"}},else2:{"*":{action_:"copy"}}}),actions:{"color-output":function(t,e){return{type_:"color",color1:e[0],color2:a.go(e[1],"bd")}}}},oxidation:{transitions:a.createTransitions({empty:{"*":{}},"roman numeral":{"*":{action_:"roman-numeral"}},"${(...)}$|$(...)$":{"*":{action_:"tex-math"}},else:{"*":{action_:"copy"}}}),actions:{"roman-numeral":function(t,e){return{type_:"roman numeral",p1:e||""}}}},"tex-math":{transitions:a.createTransitions({empty:{"*":{action_:"output"}},"\\ce{(...)}":{"*":{action_:["output","ce"]}},"{...}|\\,|\\x{}{}|\\x{}|\\x":{"*":{action_:"o="}},else:{"*":{action_:"o="}}}),actions:{output:function(t){if(t.o){var e={type_:"tex-math",p1:t.o};for(var n in t)delete t[n];return e}}}},"tex-math tight":{transitions:a.createTransitions({empty:{"*":{action_:"output"}},"\\ce{(...)}":{"*":{action_:["output","ce"]}},"{...}|\\,|\\x{}{}|\\x{}|\\x":{"*":{action_:"o="}},"-|+":{"*":{action_:"tight operator"}},else:{"*":{action_:"o="}}}),actions:{"tight operator":function(t,e){t.o=(t.o||"")+"{"+e+"}"},output:function(t){if(t.o){var e={type_:"tex-math",p1:t.o};for(var n in t)delete t[n];return e}}}},"9,9":{transitions:a.createTransitions({empty:{"*":{}},",":{"*":{action_:"comma"}},else:{"*":{action_:"copy"}}}),actions:{comma:function(){return{type_:"commaDecimal"}}}},pu:{transitions:a.createTransitions({empty:{"*":{action_:"output"}},space$:{"*":{action_:["output","space"]}},"{[(|)]}":{"0|a":{action_:"copy"}},"(-)(9)^(-9)":{0:{action_:"number^",nextState:"a"}},"(-)(9.,9)(e)(99)":{0:{action_:"enumber",nextState:"a"}},space:{"0|a":{}},"pm-operator":{"0|a":{action_:{type_:"operator",option:"\\pm"},nextState:"0"}},operator:{"0|a":{action_:"copy",nextState:"0"}},"//":{d:{action_:"o=",nextState:"/"}},"/":{d:{action_:"o=",nextState:"/"}},"{...}|else":{"0|d":{action_:"d=",nextState:"d"},a:{action_:["space","d="],nextState:"d"},"/|q":{action_:"q=",nextState:"q"}}}),actions:{enumber:function(t,e){var n=[];return"+-"===e[0]||"+/-"===e[0]?n.push("\\pm "):e[0]&&n.push(e[0]),e[1]&&(a.concatArray(n,a.go(e[1],"pu-9,9")),e[2]&&(e[2].match(/[,.]/)?a.concatArray(n,a.go(e[2],"pu-9,9")):n.push(e[2])),e[3]=e[4]||e[3],e[3]&&(e[3]=e[3].trim(),"e"===e[3]||"*"===e[3].substr(0,1)?n.push({type_:"cdot"}):n.push({type_:"times"}))),e[3]&&n.push("10^{"+e[5]+"}"),n},"number^":function(t,e){var n=[];return"+-"===e[0]||"+/-"===e[0]?n.push("\\pm "):e[0]&&n.push(e[0]),a.concatArray(n,a.go(e[1],"pu-9,9")),n.push("^{"+e[2]+"}"),n},operator:function(t,e,n){return{type_:"operator",kind_:n||e}},space:function(){return{type_:"pu-space-1"}},output:function(t){var e,n=a.patterns.match_("{(...)}",t.d||"");n&&""===n.remainder&&(t.d=n.match_);var o=a.patterns.match_("{(...)}",t.q||"");if(o&&""===o.remainder&&(t.q=o.match_),t.d&&(t.d=t.d.replace(/\u00B0C|\^oC|\^{o}C/g,"{}^{\\circ}C"),t.d=t.d.replace(/\u00B0F|\^oF|\^{o}F/g,"{}^{\\circ}F")),t.q){t.q=t.q.replace(/\u00B0C|\^oC|\^{o}C/g,"{}^{\\circ}C"),t.q=t.q.replace(/\u00B0F|\^oF|\^{o}F/g,"{}^{\\circ}F");var r={d:a.go(t.d,"pu"),q:a.go(t.q,"pu")};"//"===t.o?e={type_:"pu-frac",p1:r.d,p2:r.q}:(e=r.d,r.d.length>1||r.q.length>1?e.push({type_:" / "}):e.push({type_:"/"}),a.concatArray(e,r.q))}else e=a.go(t.d,"pu-2");for(var i in t)delete t[i];return e}}},"pu-2":{transitions:a.createTransitions({empty:{"*":{action_:"output"}},"*":{"*":{action_:["output","cdot"],nextState:"0"}},"\\x":{"*":{action_:"rm="}},space:{"*":{action_:["output","space"],nextState:"0"}},"^{(...)}|^(-1)":{1:{action_:"^(-1)"}},"-9.,9":{0:{action_:"rm=",nextState:"0"},1:{action_:"^(-1)",nextState:"0"}},"{...}|else":{"*":{action_:"rm=",nextState:"1"}}}),actions:{cdot:function(){return{type_:"tight cdot"}},"^(-1)":function(t,e){t.rm+="^{"+e+"}"},space:function(){return{type_:"pu-space-2"}},output:function(t){var e=[];if(t.rm){var n=a.patterns.match_("{(...)}",t.rm||"");e=n&&""===n.remainder?a.go(n.match_,"pu"):{type_:"rm",p1:t.rm}}for(var o in t)delete t[o];return e}}},"pu-9,9":{transitions:a.createTransitions({empty:{0:{action_:"output-0"},o:{action_:"output-o"}},",":{0:{action_:["output-0","comma"],nextState:"o"}},".":{0:{action_:["output-0","copy"],nextState:"o"}},else:{"*":{action_:"text="}}}),actions:{comma:function(){return{type_:"commaDecimal"}},"output-0":function(t){var e=[];if(t.text_=t.text_||"",t.text_.length>4){var n=t.text_.length%3;0===n&&(n=3);for(var o=t.text_.length-3;o>0;o-=3)e.push(t.text_.substr(o,3)),e.push({type_:"1000 separator"});e.push(t.text_.substr(0,n)),e.reverse()}else e.push(t.text_);for(var a in t)delete t[a];return e},"output-o":function(t){var e=[];if(t.text_=t.text_||"",t.text_.length>4){for(var n=t.text_.length-3,o=0;o":case"\u2192":case"\u27f6":return"rightarrow";case"<-":return"leftarrow";case"<->":return"leftrightarrow";case"<--\x3e":return"rightleftarrows";case"<=>":case"\u21cc":return"rightleftharpoons";case"<=>>":return"rightequilibrium";case"<<=>":return"leftequilibrium";default:throw["MhchemBugT","mhchem bug T. Please report."]}},_getBond:function(t){switch(t){case"-":case"1":return"{-}";case"=":case"2":return"{=}";case"#":case"3":return"{\\equiv}";case"~":return"{\\tripledash}";case"~-":return"{\\mathrlap{\\raisebox{-.1em}{$-$}}\\raisebox{.1em}{$\\tripledash$}}";case"~=":case"~--":return"{\\mathrlap{\\raisebox{-.2em}{$-$}}\\mathrlap{\\raisebox{.2em}{$\\tripledash$}}-}";case"-~-":return"{\\mathrlap{\\raisebox{-.2em}{$-$}}\\mathrlap{\\raisebox{.2em}{$-$}}\\tripledash}";case"...":return"{{\\cdot}{\\cdot}{\\cdot}}";case"....":return"{{\\cdot}{\\cdot}{\\cdot}{\\cdot}}";case"->":return"{\\rightarrow}";case"<-":return"{\\leftarrow}";case"<":return"{<}";case">":return"{>}";default:throw["MhchemBugT","mhchem bug T. Please report."]}},_getOperator:function(t){switch(t){case"+":return" {}+{} ";case"-":return" {}-{} ";case"=":return" {}={} ";case"<":return" {}<{} ";case">":return" {}>{} ";case"<<":return" {}\\ll{} ";case">>":return" {}\\gg{} ";case"\\pm":return" {}\\pm{} ";case"\\approx":case"$\\approx$":return" {}\\approx{} ";case"v":case"(v)":return" \\downarrow{} ";case"^":case"(^)":return" \\uparrow{} ";default:throw["MhchemBugT","mhchem bug T. Please report."]}}}}(),a=a.default}()}));
\ No newline at end of file
diff --git a/lib/lightgallery/fonts/lg.svg b/lib/lightgallery/fonts/lg.svg
new file mode 100644
index 00000000..fe8b0756
--- /dev/null
+++ b/lib/lightgallery/fonts/lg.svg
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lib/lightgallery/fonts/lg.ttf b/lib/lightgallery/fonts/lg.ttf
new file mode 100644
index 00000000..825f4832
Binary files /dev/null and b/lib/lightgallery/fonts/lg.ttf differ
diff --git a/lib/lightgallery/fonts/lg.woff b/lib/lightgallery/fonts/lg.woff
new file mode 100644
index 00000000..fd02a6f4
Binary files /dev/null and b/lib/lightgallery/fonts/lg.woff differ
diff --git a/lib/lightgallery/fonts/lg.woff2 b/lib/lightgallery/fonts/lg.woff2
new file mode 100644
index 00000000..2c2e2892
Binary files /dev/null and b/lib/lightgallery/fonts/lg.woff2 differ
diff --git a/lib/lightgallery/images/loading.gif b/lib/lightgallery/images/loading.gif
new file mode 100644
index 00000000..4744c455
Binary files /dev/null and b/lib/lightgallery/images/loading.gif differ
diff --git a/lib/sharer/sharer.min.js b/lib/sharer/sharer.min.js
new file mode 100644
index 00000000..953a4517
--- /dev/null
+++ b/lib/sharer/sharer.min.js
@@ -0,0 +1 @@
+(function(m,r){"use strict";var s=function(t){this.elem=t};s.init=function(){var t=r.querySelectorAll("[data-sharer]"),e,a=t.length;for(e=0;e0?"?":"";for(r=0;r Array.isArray(e),t=t=>e(t)?t:[t];const n=e=>Array.from(e),r=e=>document.createTextNode(e);let i=e=>([...e.childNodes].forEach((e=>{if(e.nodeValue)return[...e.nodeValue].forEach((t=>{e.parentNode.insertBefore(r(t),e)})),void e.remove();i(e)})),e);const a=e=>{let t=document.implementation.createHTMLDocument();return t.body.innerHTML=e,i(t.body)},o="data-typeit-id",s="ti-cursor",l={started:!1,completed:!1,frozen:!1,destroyed:!1},u={breakLines:!0,cursor:{autoPause:!0,autoPauseDelay:500,animation:{frames:[0,0,1].map((e=>({opacity:e}))),options:{iterations:1/0,easing:"steps(2, start)",fill:"forwards"}}},cursorChar:"|",cursorSpeed:1e3,deleteSpeed:null,html:!0,lifeLike:!0,loop:!1,loopDelay:750,nextStringDelay:750,speed:100,startDelay:250,startDelete:!1,strings:[],waitUntilVisible:!1,beforeString:()=>{},afterString:()=>{},beforeStep:()=>{},afterStep:()=>{},afterComplete:()=>{}},c=`[${o}]:before {content: '.'; display: inline-block; width: 0; visibility: hidden;}`;function d(e,t=!1,n=!1){let r,i=e.querySelector(`.${s}`),a=document.createTreeWalker(e,NodeFilter.SHOW_ALL,{acceptNode:e=>{if(i&&n){if(e.classList?.contains(s))return NodeFilter.FILTER_ACCEPT;if(i.contains(e))return NodeFilter.FILTER_REJECT}return e.classList?.contains(s)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT}}),o=[];for(;r=a.nextNode();)r.originalParent||(r.originalParent=r.parentNode),o.push(r);return t?o.reverse():o}function f(e,t=!0){return t?d(a(e)):n(e).map(r)}const h=e=>document.createElement(e),y=(e,t="")=>{let n=h("style");n.id=t,n.appendChild(r(e)),document.head.appendChild(n)},p=t=>(e(t)||(t=[t/2,t/2]),t),m=(e,t)=>Math.abs(Math.random()*(e+t-(e-t))+(e-t));let g=e=>e/2;const b=e=>"value"in e;let w=e=>"function"==typeof e?e():e;const T=e=>Number.isInteger(e);let v=(e,t=document,n=!1)=>t["querySelector"+(n?"All":"")](e);const E=(e,t)=>Object.assign({},e,t);let P={"font-family":"","font-weight":"","font-size":"","font-style":"","line-height":"",color:"",transform:"translateX(-.125em)"};const S=(e,t)=>new Array(t).fill(e),N=({queueItems:e,selector:t,cursorPosition:n,to:r})=>{if(T(t))return-1*t;let i=new RegExp("END","i").test(r),a=t?[...e].reverse().findIndex((({char:e})=>{let n=e.parentElement,r=n.matches(t);return!(!i||!r)||r&&n.firstChild.isSameNode(e)})):-1;return a<0&&(a=i?0:e.length-1),a-n+(i?0:1)};let L=e=>new Promise((t=>{requestAnimationFrame((async()=>{t(await e())}))})),C=e=>e?.getAnimations().find((t=>t.id===e.dataset.tiAnimationId)),D=({cursor:e,frames:t,options:n})=>{let r=e.animate(t,n);return r.pause(),r.id=e.dataset.tiAnimationId,L((()=>{L((()=>{r.play()}))})),r},I=e=>e.func?.call(null),M=async({index:e,queueItems:t,wait:n,cursor:r,cursorOptions:i})=>{let a=t[e][1],o=[],s=e,l=a,u=()=>l&&!l.delay,c=a.shouldPauseCursor()&&i.autoPause;for(;u();)o.push(l),u()&&s++,l=t[s]?t[s][1]:null;if(o.length)return await L((async()=>{for(let e of o)await I(e)})),s-1;let d,f=C(r);return f&&(d={...f.effect.getComputedTiming(),delay:c?i.autoPauseDelay:0}),await n((async()=>{f&&c&&f.cancel(),await L((()=>{I(a)}))}),a.delay),await(({cursor:e,options:t,cursorOptions:n})=>{if(!e||!n)return;let r,i=C(e);i&&(t.delay=i.effect.getComputedTiming().delay,r=i.currentTime,i.cancel());let a=D({cursor:e,frames:n.animation.frames,options:t});return r&&(a.currentTime=r),a})({cursor:r,options:d,cursorOptions:i}),e};return function(e,r={}){let L=async(e,t,n=!1)=>{K.frozen&&await new Promise((e=>{this.unfreeze=()=>{K.frozen=!1,e()}})),n||await Y.beforeStep(this),await((e,t,n)=>new Promise((r=>{n.push(setTimeout((async()=>{await e(),r()}),t||0))})))(e,t,W),n||await Y.afterStep(this)},C=(e,t)=>M({index:e,queueItems:t,wait:L,cursor:ne,cursorOptions:Y.cursor}),I=e=>((e,t)=>{if(!e)return;let n=e.parentNode;(n.childNodes.length>1||n.isSameNode(t)?e:n).remove()})(e,J),x=()=>b(J),A=(e=0)=>function(e){let{speed:t,deleteSpeed:n,lifeLike:r}=e;return n=null!==n?n:t/3,r?[m(t,g(t)),m(n,g(n))]:[t,n]}(Y)[e],$=()=>(e=>b(e)?n(e.value):d(e,!0).filter((e=>!(e.childNodes.length>0))))(J),H=(e,t)=>(ee.add(e),((e={})=>{let t=e.delay;t&&ee.add({delay:t})})(t),this),O=()=>G??X,F=(e={})=>[{func:()=>j(e)},{func:()=>j(Y)}],k=e=>{let t=Y.nextStringDelay;ee.add([{delay:t[0]},...e,{delay:t[1]}])},R=async()=>{if(!x()&&ne&&J.appendChild(ne),te){((e,t)=>{let n=`[${o}='${e}'] .${s}`,r=getComputedStyle(t),i=Object.entries(P).reduce(((e,[t,n])=>`${e} ${t}: var(--ti-cursor-${t}, ${n||r[t]});`),"");y(`${n} { display: inline-block; width: 0; ${i} }`,e)})(Z,J),ne.dataset.tiAnimationId=Z;let{animation:e}=Y.cursor,{frames:t,options:n}=e;D({frames:t,cursor:ne,options:{duration:Y.cursorSpeed,...n}})}},q=()=>{let e=Y.strings.filter((e=>!!e));e.forEach(((t,n)=>{if(this.type(t),n+1===e.length)return;let r=Y.breakLines?[{func:()=>_(h("BR")),typeable:!0}]:S({func:Q,delay:A(1)},ee.getTypeable().length);k(r)}))},z=async(e=!0)=>{K.started=!0;let t=t=>{ee.done(t,!e)};try{let n=[...ee.getQueue()];for(let e=0;ee+t)).forEach((e=>{let[r]=n[e];t(r)})),e=r}t(r)}}if(!e)return this;if(K.completed=!0,await Y.afterComplete(this),!Y.loop)throw"";let r=Y.loopDelay;L((async()=>{await(async e=>{let t=O();t&&await B({value:t});let n=$().map((e=>[Symbol(),{func:Q,delay:A(1),deletable:!0,shouldPauseCursor:()=>!0}]));for(let r=0;r{var t,n,r;t=e,n=X,r=$(),X=Math.min(Math.max(n+t,0),r.length),((e,t,n)=>{let r=t[n-1],i=v(`.${s}`,e);(e=r?.parentNode||e).insertBefore(i,r||null)})(J,$(),X)},_=e=>((e,t)=>{if(b(e))return void(e.value=`${e.value}${t.textContent}`);t.innerHTML="";let n=(r=t.originalParent,/body/i.test(r?.tagName)?e:t.originalParent||e);var r;n.insertBefore(t,v("."+s,n)||null)})(J,e),j=async e=>Y=E(Y,e),V=async()=>{x()?J.value="":$().forEach(I)},Q=()=>{let e=$();e.length&&(x()?J.value=J.value.slice(0,-1):I(e[X]))};this.break=function(e){return H({func:()=>_(h("BR")),typeable:!0},e)},this.delete=function(e=null,t={}){e=w(e);let n=F(t),r=e,{instant:i,to:a}=t,o=ee.getTypeable(),s=null===r?o.length:T(r)?r:N({queueItems:o,selector:r,cursorPosition:O(),to:a});return H([n[0],...S({func:Q,delay:i?0:A(1),deletable:!0},s),n[1]],t)},this.empty=function(e={}){return H({func:V},e)},this.exec=function(e,t={}){let n=F(t);return H([n[0],{func:()=>e(this)},n[1]],t)},this.move=function(e,t={}){e=w(e);let n=F(t),{instant:r,to:i}=t,a=N({queueItems:ee.getTypeable(),selector:null===e?"":e,to:i,cursorPosition:O()}),o=a<0?-1:1;return G=O()+a,H([n[0],...S({func:()=>B(o),delay:r?0:A(),cursorable:!0},Math.abs(a)),n[1]],t)},this.options=function(e,t={}){return e=w(e),j(e),H({},t)},this.pause=function(e,t={}){return H({delay:w(e)},t)},this.type=function(e,t={}){e=w(e);let{instant:n}=t,r=F(t),i=f(e,Y.html).map((e=>{return{func:()=>_(e),char:e,delay:n||(t=e,/<(.+)>(.*?)<\/(.+)>/.test(t.outerHTML))?0:A(),typeable:e.nodeType===Node.TEXT_NODE};var t})),a=[r[0],{func:async()=>await Y.beforeString(e,this)},...i,{func:async()=>await Y.afterString(e,this)},r[1]];return H(a,t)},this.is=function(e){return K[e]},this.destroy=function(e=!0){W.forEach(clearTimeout),W=[],w(e)&&ne&&I(ne),K.destroyed=!0},this.freeze=function(){K.frozen=!0},this.unfreeze=()=>{},this.reset=function(e){!this.is("destroyed")&&this.destroy(),e?(ee.wipe(),e(this)):ee.reset(),X=0;for(let t in K)K[t]=!1;return J[x()?"value":"innerHTML"]="",this},this.go=function(){return K.started?this:(R(),Y.waitUntilVisible?(((e,t)=>{new IntersectionObserver(((n,r)=>{n.forEach((n=>{n.isIntersecting&&(t(),r.unobserve(e))}))}),{threshold:1}).observe(e)})(J,z.bind(this)),this):(z(),this))},this.flush=function(e=(()=>{})){return R(),z(!1).then(e),this},this.getQueue=()=>ee,this.getOptions=()=>Y,this.updateOptions=e=>j(e),this.getElement=()=>J;let J="string"==typeof(U=e)?v(U):U;var U;let W=[],X=0,G=null,K=E({},l);r.cursor=(e=>{if("object"==typeof e){let t={},{frames:n,options:r}=u.cursor.animation;return t.animation=e.animation||{},t.animation.frames=e.animation?.frames||n,t.animation.options=E(r,e.animation?.options||{}),t.autoPause=e.autoPause??u.cursor.autoPause,t.autoPauseDelay=e.autoPauseDelay||u.cursor.autoPauseDelay,t}return!0===e?u.cursor:e})(r.cursor??u.cursor);let Y=E(u,r);Y=E(Y,{html:!x()&&Y.html,nextStringDelay:p(Y.nextStringDelay),loopDelay:p(Y.loopDelay)});let Z=Math.random().toString().substring(2,9),ee=function(e){let n=function(e){return t(e).forEach((e=>a.set(Symbol(e.char?.innerText),r({...e})))),this},r=e=>(e.shouldPauseCursor=function(){return Boolean(this.typeable||this.cursorable||this.deletable)},e),i=()=>Array.from(a.values()),a=new Map;return n(e),{add:n,set:function(e,t){let n=[...a.keys()];a.set(n[e],r(t))},wipe:function(){a=new Map,n(e)},reset:function(){a.forEach((e=>delete e.done))},destroy:e=>a.delete(e),done:(e,t=!1)=>t?a.delete(e):a.get(e).done=!0,getItems:(e=!1)=>e?i():i().filter((e=>!e.done)),getQueue:()=>a,getTypeable:()=>i().filter((e=>e.typeable))}}([{delay:Y.startDelay}]);J.dataset.typeitId=Z,y(c);let te=!!Y.cursor&&!x(),ne=(()=>{if(x())return;let e=h("span");return e.className=s,te?(e.innerHTML=a(Y.cursorChar).innerHTML,e):(e.style.visibility="hidden",e)})();Y.strings=(e=>{let t=J.innerHTML;return t?(J.innerHTML="",Y.startDelete?(J.innerHTML=t,i(J),k(S({func:Q,delay:A(1),deletable:!0},$().length)),e):t.replace(//g,"").trim().split(/ /).concat(e)):e})(t(Y.strings)),Y.strings.length&&q()}}));
diff --git a/lib/webfonts/fa-brands-400.ttf b/lib/webfonts/fa-brands-400.ttf
new file mode 100644
index 00000000..502f3621
Binary files /dev/null and b/lib/webfonts/fa-brands-400.ttf differ
diff --git a/lib/webfonts/fa-brands-400.woff2 b/lib/webfonts/fa-brands-400.woff2
new file mode 100644
index 00000000..d801b51f
Binary files /dev/null and b/lib/webfonts/fa-brands-400.woff2 differ
diff --git a/lib/webfonts/fa-regular-400.ttf b/lib/webfonts/fa-regular-400.ttf
new file mode 100644
index 00000000..e0abe271
Binary files /dev/null and b/lib/webfonts/fa-regular-400.ttf differ
diff --git a/lib/webfonts/fa-regular-400.woff2 b/lib/webfonts/fa-regular-400.woff2
new file mode 100644
index 00000000..d736e4b2
Binary files /dev/null and b/lib/webfonts/fa-regular-400.woff2 differ
diff --git a/lib/webfonts/fa-solid-900.ttf b/lib/webfonts/fa-solid-900.ttf
new file mode 100644
index 00000000..13c94897
Binary files /dev/null and b/lib/webfonts/fa-solid-900.ttf differ
diff --git a/lib/webfonts/fa-solid-900.woff2 b/lib/webfonts/fa-solid-900.woff2
new file mode 100644
index 00000000..3516fdbe
Binary files /dev/null and b/lib/webfonts/fa-solid-900.woff2 differ
diff --git a/lib/webfonts/fa-v4compatibility.ttf b/lib/webfonts/fa-v4compatibility.ttf
new file mode 100644
index 00000000..dc298194
Binary files /dev/null and b/lib/webfonts/fa-v4compatibility.ttf differ
diff --git a/lib/webfonts/fa-v4compatibility.woff2 b/lib/webfonts/fa-v4compatibility.woff2
new file mode 100644
index 00000000..28d46b15
Binary files /dev/null and b/lib/webfonts/fa-v4compatibility.woff2 differ
diff --git a/page/1/index.html b/page/1/index.html
new file mode 100644
index 00000000..451fbda8
--- /dev/null
+++ b/page/1/index.html
@@ -0,0 +1 @@
+https://dingyufan.github.io/
\ No newline at end of file
diff --git a/posts/dubbo-stack-overflow-due-to-jdk8-instant/index.html b/posts/dubbo-stack-overflow-due-to-jdk8-instant/index.html
new file mode 100644
index 00000000..4f39a68a
--- /dev/null
+++ b/posts/dubbo-stack-overflow-due-to-jdk8-instant/index.html
@@ -0,0 +1,1219 @@
+JDK8日期时间API导致Dubbo调用StackOverflow错误 - dingyufan's blog 2022-04-30 2023-07-28 约 10159 字
0 前言 博客搭建之后,不知道从何写起。思来想去,决定从记录一些工作中遇到的问题开始。一来对问题有个归档,算是有一些积累,方便日后回顾;二来促使自己遇到问题时能探本朔源,做到知其然知其所以然,不要草草百度了事。不积跬步,无以至千里;不积小流,无以成江海。
1 问题 最近项目中使用dubbo(v2.5.3 )的时候,遇到一个问题:dubbo服务方在对response编码时失败,发生了栈溢出异常。可以通过一个demo(dubbo-stack-overflow-due-to-jkd8-instant )复现这个异常。
看到栈溢出,一般想到的情况要么是递归过深,或是循环调用方法次数过多。为什么会在dubbo序列化编码的时候发生这个异常呢?通过查看源码提交记录,结合异常信息Fail to encode repsonse,并且在异常栈中出现JavaSerializer类,判断问题出在Dubbo在对Response序列化时无法处理Instant类型对象 。
网上搜索到的博客基本上只是说了有这样的问题,但是没有说明具体的原因,都是草草改用Date了事。通过这篇博客,记录一下Instant类型对象引起Dubbo对Response序列化失败导致栈溢出的根本原因以及解决方案。
3 Dubbo序列化Response dubbo对不同协议、不同序列化方式处理方式是不一样的。此项目使用默认协议:dubbo协议 ,同时也采用dubbo协议默认序列化方式**:hessian2**。下文的序列化过程分析就是基于此的。
1
+ 2
+
<!-- dubbo协议hessian2序列化方式,是dubbo的默认值。所以在xml配置中写不写这行配置都可以 -->
+<dubbo:protocol name= "dubbo" serialization= "hessian2" />
**参考官网文档 2.4 服务提供方返回调用结果 **,我们从Dubbo处理返回结果入手。
3.1 ExchangeCodec / DubboCodec ExchangeCodec是Dubbo exchange信息交换层 的编码器。ExchangeCodec#encode(Channel, ChannelBuffer, Object)
方法是Dubbo对Request和Response进行编码的入口,逻辑很简单,主要是根据消息的类型,交由对应方法进行编码,对于不是自己负责的非Request、Response类型的消息对象,交给父类处理。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+
// ExchangeCodec.java
+ public void encode ( Channel channel , ChannelBuffer buffer , Object msg ) throws IOException {
+ if ( msg instanceof Request ) {
+ encodeRequest ( channel , buffer , ( Request ) msg );
+ } else if ( msg instanceof Response ) {
+ // 对response对象进行编码
+ encodeResponse ( channel , buffer , ( Response ) msg );
+ } else {
+ super . encode ( channel , buffer , msg );
+ }
+ }
ExchangeCodec#encodeResponse(Channel, ChannelBuffer, Response)
方法主要做的就是组装消息写入ChannelBuffer,这样消息就可以发送给调用方。消息两部分组成:消息头 ,包含状态序列化等相关信息的字节数组;消息体 :Response返回的具体数据转换成的字节数组。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+
// ExchangeCodec.java
+ protected void encodeResponse ( Channel channel , ChannelBuffer buffer , Response res ) throws IOException {
+ try {
+ // 获取序列化方式*
+ Serialization serialization = getSerialization ( channel );
+ // 消息头字节数组。长度16
+ byte [] header = new byte [ HEADER_LENGTH ];
+ // 消息头 写入魔数
+ Bytes . short2bytes ( MAGIC , header );
+ // 消息头 写入序列化器ID信息
+ header [ 2 ] = serialization . getContentTypeId ();
+ if ( res . isHeartbeat ()) header [ 2 ] |= FLAG_EVENT ;
+ byte status = res . getStatus ();
+ // 消息头 写入状态
+ header [ 3 ] = status ;
+ // 消息头 写入
+ Bytes . long2bytes ( res . getId (), header , 4 );
+
+ int savedWriteIndex = buffer . writerIndex ();
+ buffer . writerIndex ( savedWriteIndex + HEADER_LENGTH );
+ ChannelBufferOutputStream bos = new ChannelBufferOutputStream ( buffer );
+ // 得到序列化器*
+ ObjectOutput out = serialization . serialize ( channel . getUrl (), bos );
+ if ( status == Response . OK ) {
+ if ( res . isHeartbeat ()) {
+ encodeHeartbeatData ( channel , out , res . getResult ());
+ } else {
+ // 对调用结果进行序列化*
+ // 这个方法会调用子类DubboCodec的override方法实现
+ encodeResponseData ( channel , out , res . getResult ());
+ }
+ }
+ else out . writeUTF ( res . getErrorMessage ());
+ out . flushBuffer ();
+ bos . flush ();
+ bos . close ();
+ // 消息体字节长度
+ int len = bos . writtenBytes ();
+ checkPayload ( channel , len );
+ Bytes . int2bytes ( len , header , 12 );
+ // 先写完消息体,再写消息头,再设置writeIndex位置
+ buffer . writerIndex ( savedWriteIndex );
+ buffer . writeBytes ( header );
+ buffer . writerIndex ( savedWriteIndex + HEADER_LENGTH + len );
+ } catch ( Throwable t ) {
+ // 略...
+ }
+}
在这里先不关注消息头、ChannelBuffer等操作,这次主要看序列化相关的几个步骤:
3.1.1 获取序列化方式 这里实际是父类中的方法AbstractCodec#getSerialization(Channel)
方法。这一步是利用Dubbo SPI机制,实例化一个序列化方式对象。具体过程不是本次的重点,只要理解这一步会根据<dubbo:protocol>
配置的序列化方式返回对应的序列化方式对象。比如这里得到的就是Hessian2Serialization对象。
3.1.2 得到序列化器 这里调用Serialization#serialize(URL, OutputStream)
方法,得到一个序列化器。hessian2序列化提供的序列化器是Hessian2ObjectOutput对象,通过构造函数传入了ExchangeCodec#encodeResponse(Channel, ChannelBuffer, Response)
方法中定义的包装了buffer的bos。这样Hessian2ObjectOutput对象可以将具体数据写入到buffer中。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+
public class Hessian2Serialization implements Serialization {
+
+ public static final byte ID = 2 ;
+
+ public byte getContentTypeId () {
+ return ID ;
+ }
+
+ public String getContentType () {
+ return "x-application/hessian2" ;
+ }
+
+ public ObjectOutput serialize ( URL url , OutputStream out ) throws IOException {
+ // 序列化器对象
+ return new Hessian2ObjectOutput ( out );
+ }
+
+ public ObjectInput deserialize ( URL url , InputStream is ) throws IOException {
+ return new Hessian2ObjectInput ( is );
+ }
+ }
3.1.3 对调用结果进行序列化 配置中指定的是dubbo协议,这里实际会被调用的是DubboCodec#encodeResponseData(Channel, ObjectOutput, Object)
重写方法。DubboCodec继承自ExchangeCodec,是Dubbo protocol远程调用层 的编码器,主要负责实现协议层面的Response数据编码。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+
// DubboCodec.java
+ @Override
+protected void encodeResponseData ( Channel channel , ObjectOutput out , Object data ) throws IOException {
+ Result result = ( Result ) data ;
+
+ Throwable th = result . getException ();
+ if ( th == null ) {
+ Object ret = result . getValue ();
+ if ( ret == null ) {
+ out . writeByte ( RESPONSE_NULL_VALUE );
+ } else {
+ // 序列化器中写入response数据标志位
+ out . writeByte ( RESPONSE_VALUE );
+ // 序列化器中写入正常返回的对象
+ out . writeObject ( ret );
+ }
+ } else {
+ out . writeByte ( RESPONSE_WITH_EXCEPTION );
+ out . writeObject ( th );
+ }
+ }
3.2 Hessian2ObjectOutput 在3.1.2中提到过,得到的序列化器是Hessian2ObjectOutput,实现了ObjectOutput接口。对于常见的简单数据对象,ObjectOutput接口中都定义了相应处理方法,同时还定义writeObject(Object)方法处理复杂对象的序列化。
Hessian2ObjectOutput实现了接口定义的各个方法,通过一个Hessian2Output对象,完成各种数据类型hessian2序列化的具体操作。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+
public class Hessian2ObjectOutput implements ObjectOutput {
+ private final Hessian2Output mH2o ;
+
+ public Hessian2ObjectOutput ( OutputStream os ) {
+ mH2o = new Hessian2Output ( os );
+ mH2o . setSerializerFactory ( Hessian2SerializerFactory . SERIALIZER_FACTORY );
+ }
+
+ public void writeBool ( boolean v ) throws IOException {
+ mH2o . writeBoolean ( v );
+ }
+ // 略...
+
+ public void writeObject ( Object obj ) throws IOException {
+ mH2o . writeObject ( obj );
+ }
+
+ public void flushBuffer () throws IOException {
+ mH2o . flushBuffer ();
+ }
+ }
Hessian2ObjectOutput#writeObject(Object)
方法是response数据对象序列化的入口 ,数据对象序列化的操作从这一步正式开始。这个方法中调用的是Hessian2Output#writeObject(Object)
方法。
3.3 Hessian2Output 注意Hessian2Output的方法,如writeBoolean(boolean)
,对值进行编码写入的时候,没有直接写入 _os
,而是维护一个字节数组 _buffer
和 _offset
值。回头看3.1中的ExchangeCodec#encodeResponse(Channel, ChannelBuffer, Response)
方法,会调用out.flushBuffer()
,通过Hessian2ObjectOutput来调用Hessian2Output#flushBuffer()
方法,将 _buffer
内容写入 _os
,即写入 bos
,即ChannelBuffer对象buffer
。
Hessian2Output#writeObject(Object)
方法不像其他方法,没有直接向_buffer
写入具体的字节,而是尝试获取一个Serializer对象,完成对象的序列化。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+
public class Hessian2Output extends AbstractHessianOutput implements Hessian2Constants {
+ protected OutputStream _os ;
+ private final byte [] _buffer = new byte [ SIZE ];
+ // 略...
+
+ public Hessian2Output ( OutputStream os ) {
+ _os = os ;
+ }
+ // 略...
+
+ public void writeBoolean ( boolean value ) throws IOException {
+ if ( SIZE < _offset + 16 )
+ flush ();
+
+ if ( value )
+ // 不直接写入_os
+ _buffer [ _offset ++] = ( byte ) 'T' ;
+ else
+ _buffer [ _offset ++] = ( byte ) 'F' ;
+ }
+
+ public void writeObject ( Object object ) throws IOException {
+ if ( object == null ) {
+ writeNull ();
+ return ;
+ }
+
+ Serializer serializer ;
+ // 获得序列化工厂类,获得一个Serializer对象
+ serializer = findSerializerFactory (). getSerializer ( object . getClass ());
+ // 完成对象的序列化
+ serializer . writeObject ( object , this );
+ }
+
+ public final void flushBuffer () throws IOException {
+ int offset = _offset ;
+ if (! _isStreaming && offset > 0 ) {
+ _offset = 0 ;
+ _os . write ( _buffer , 0 , offset );
+ } else if ( _isStreaming && offset > 3 ) {
+ int len = offset - 3 ;
+ _buffer [ 0 ] = 'p' ;
+ _buffer [ 1 ] = ( byte ) ( len >> 8 );
+ _buffer [ 2 ] = ( byte ) len ;
+ _offset = 3 ;
+
+ _os . write ( _buffer , 0 , offset );
+ }
+ }
+ }
findSerializerFactory()会从父类获得一个SerializerFactory对象,回头从3.2看到,在Hessian2ObjectOutput中,为Hessian2Output指定SerializerFactory 具体对象为Hessian2SerializerFactory.SERIALIZER_FACTORY
。我们主要来看getSerializer(object.getClass())
3.4 SerializerFactory / Hessian2SerializerFactory Hessian2SerializerFactory相对比较简单,只是实现了getClassLoader() 方法,然后在公共静态常量量中构造了一个Hessian2SerializerFactory对象供使用。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+
public class Hessian2SerializerFactory extends SerializerFactory {
+
+ public static final SerializerFactory SERIALIZER_FACTORY = new Hessian2SerializerFactory ();
+
+ private Hessian2SerializerFactory () {
+ }
+
+ @Override
+ public ClassLoader getClassLoader () {
+ return Thread . currentThread (). getContextClassLoader ();
+ }
+
+ }
他父类SerializerFactory功能更加丰富。SerializerFactory#getSerializer(Class)
方法比较长,但是其实整体逻辑并不复杂:根据类型Class返回相应的Serializer实例,如果类型和方法中列举的都不匹配,则会返回一个默认的Serializer对象,即 JavaSerializer对象 。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+
public class SerializerFactory extends AbstractSerializerFactory {
+ // 略...
+
+ public Serializer getSerializer ( Class cl ) throws HessianProtocolException {
+ Serializer serializer ;
+ serializer = ( Serializer ) _staticSerializerMap . get ( cl );
+
+ if ( serializer != null )
+ return serializer ;
+
+ if ( _cachedSerializerMap != null ) {
+ synchronized ( _cachedSerializerMap ) {
+ serializer = ( Serializer ) _cachedSerializerMap . get ( cl );
+ }
+ if ( serializer != null )
+ return serializer ;
+ }
+
+ for ( int i = 0 ; serializer == null && _factories != null && i < _factories . size (); i ++) {
+ AbstractSerializerFactory factory ;
+ factory = ( AbstractSerializerFactory ) _factories . get ( i );
+ serializer = factory . getSerializer ( cl );
+ }
+
+ if ( serializer != null ) {
+
+ } else if ( JavaSerializer . getWriteReplace ( cl ) != null )
+ serializer = new JavaSerializer ( cl , _loader );
+ else if ( HessianRemoteObject . class . isAssignableFrom ( cl ))
+ serializer = new RemoteSerializer ();
+ // else if (BurlapRemoteObject.class.isAssignableFrom(cl))
+ // serializer = new RemoteSerializer();
+ else if ( Map . class . isAssignableFrom ( cl )) {
+ if ( _mapSerializer == null )
+ _mapSerializer = new MapSerializer ();
+ serializer = _mapSerializer ;
+ }
+ else if ( Collection . class . isAssignableFrom ( cl )) {
+ if ( _collectionSerializer == null ) {
+ _collectionSerializer = new CollectionSerializer ();
+ }
+ serializer = _collectionSerializer ;
+ }
+ else if ( cl . isArray ())
+ serializer = new ArraySerializer ();
+ else if ( Throwable . class . isAssignableFrom ( cl ))
+ serializer = new ThrowableSerializer ( cl , getClassLoader ());
+ else if ( InputStream . class . isAssignableFrom ( cl ))
+ serializer = new InputStreamSerializer ();
+ else if ( Iterator . class . isAssignableFrom ( cl ))
+ serializer = IteratorSerializer . create ();
+ else if ( Enumeration . class . isAssignableFrom ( cl ))
+ serializer = EnumerationSerializer . create ();
+ else if ( Calendar . class . isAssignableFrom ( cl ))
+ serializer = CalendarSerializer . create ();
+ else if ( Locale . class . isAssignableFrom ( cl ))
+ serializer = LocaleSerializer . create ();
+ else if ( Enum . class . isAssignableFrom ( cl ))
+ serializer = new EnumSerializer ( cl );
+
+ if ( serializer == null )
+ // 都不符合以上类似,则返回默认的serializer
+ serializer = getDefaultSerializer ( cl );
+
+ if ( _cachedSerializerMap == null )
+ _cachedSerializerMap = new HashMap ( 8 );
+
+ synchronized ( _cachedSerializerMap ) {
+ // 缓存这个类型对应的serializer,下次直接从缓存获取
+ _cachedSerializerMap . put ( cl , serializer );
+ }
+
+ return serializer ;
+ }
+
+ protected Serializer getDefaultSerializer ( Class cl ) {
+ if ( _defaultSerializer != null )
+ return _defaultSerializer ;
+ if (! Serializable . class . isAssignableFrom ( cl ) && ! _isAllowNonSerializable ) {
+ throw new IllegalStateException ( "Serialized class " + cl . getName () + " must implement java.io.Serializable" );
+ }
+
+ return new JavaSerializer ( cl , _loader );
+ }
+ }
对于demo中的返回的数据对象DemoDTO,很显然就是会得到一个JavaSerializer,通过它的完成序列化。
3.5 JavaSerializer 回顾上文3.3节中,serializer对象完成序列哈的入库也是writeObject()方法。来看JavaSerializer#writeObject(Object, AbstractHessianOutput)
方法。这里面序列化分两种情况:一是要序列化的对象有writeReplce()方法的,直接调用writeReplce()方法完成序列化;而是常规的使用静态类FieldSerializer序列化。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+
// JavaSerializer.java
+ public void writeObject ( Object obj , AbstractHessianOutput out ) throws IOException {
+ if ( out . addRef ( obj )) {
+ return ;
+ }
+
+ Class cl = obj . getClass ();
+ try {
+ // 调用序列化对象的writeReplace方法完成序列化
+ if ( _writeReplace != null ) {
+ Object repl ;
+ if ( _writeReplaceFactory != null )
+ repl = _writeReplace . invoke ( _writeReplaceFactory , obj );
+ else
+ repl = _writeReplace . invoke ( obj );
+
+ out . removeRef ( obj );
+ out . writeObject ( repl );
+ out . replaceRef ( repl , obj );
+ return ;
+ }
+ } catch ( RuntimeException e ) {
+ throw e ;
+ } catch ( Exception e ) {
+ // log.log(Level.FINE, e.toString(), e);
+ throw new RuntimeException ( e );
+ }
+
+ // hessian2会写入对象开始标记;hessian会写map开始标记并返回-2
+ int ref = out . writeObjectBegin ( cl . getName ());
+
+ if ( ref < - 1 ) {
+ // 这种情况看起来是兼容hessian序列化的,而不是hessian2序列化的
+ // hessian序列化是把对象当做map处理,所以writeObject10()方法里会循环写入字段名称然后马上字段序列化,最后还会写一个map结束标志
+ writeObject10 ( obj , out );
+ }
+ else {
+ if ( ref == - 1 ) {
+ // 写入对象包含字段长度,以及各个类型字段信息
+ writeDefinition20 ( out );
+ // 类型信息编码处理
+ out . writeObjectBegin ( cl . getName ());
+ }
+ // 这里会开始会遍历序列化对象包含的各个字段
+ writeInstance ( obj , out );
+ }
+ }
3.5.1 writeReplace方法 JavaSerializer 使用一个Method变量 _writeReplace
保存writeReplace()方法,这个方法是从那里来的呢?在JavaSerializer构造方法中,会调用一个私有方法introspectWriteReplace(Class, ClassLoader)
。它会从两个地方找writeReplace()方法:一是序列化类相应的HessianSerializer类;二是序列化类本身 。但是要注意,HessianSerializer类的writeReplace方法签名 和 序列化类本身writeReplace方法签名 是有区别的。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+
// JavaSerializer.java
+ private void introspectWriteReplace ( Class cl , ClassLoader loader ) {
+ try {
+ String className = cl . getName () + "HessianSerializer" ;
+ // 加载 序列化类类名+HessianSerializer的类。例如demo中的DemoDTO类,则会
+ // 尝试加载cn.dingyufan.blog.demo.dubbostackoverflowduetojkd8instantprovider.api.dto.DemoDTOHessianSerializer类
+ Class serializerClass = Class . forName ( className , false , loader );
+ // 实例化相应的HessianSerializer类型
+ Object serializerObject = serializerClass . newInstance ();
+ // 反射获取WriteReplace方法
+ Method writeReplace = getWriteReplace ( serializerClass , cl );
+
+ if ( writeReplace != null ) {
+ _writeReplaceFactory = serializerObject ;
+ _writeReplace = writeReplace ;
+ return ;
+ }
+ } catch ( ClassNotFoundException e ) {
+ // 没有相应HessianSerializer类就会抛出ClassNotFoundException,这个异常不处理,后面会再次尝试从序列化类本身寻找
+ } catch ( Exception e ) {
+ log . log ( Level . FINER , e . toString (), e );
+ }
+ // 从序列化类本身找writeReplace方法
+ _writeReplace = getWriteReplace ( cl );
+}
+
+ // 从要序列化的类型寻找 方法名为writeReplace、入参为空 的方法
+ protected static Method getWriteReplace ( Class cl ) {
+ for (; cl != null ; cl = cl . getSuperclass ()) {
+ Method [] methods = cl . getDeclaredMethods ();
+
+ for ( int i = 0 ; i < methods . length ; i ++) {
+ Method method = methods [ i ];
+
+ if ( method . getName (). equals ( "writeReplace" ) &&
+ method . getParameterTypes (). length == 0 )
+ return method ;
+ }
+ }
+
+ return null ;
+ }
+
+ // 从相应HessianSerializer类找到 方法名为writeReplace、 入参有且仅为要序列化类型 的方法
+ protected Method getWriteReplace ( Class cl , Class param )
+{
+ for (; cl != null ; cl = cl . getSuperclass ()) {
+ for ( Method method : cl . getDeclaredMethods ()) {
+ if ( method . getName (). equals ( "writeReplace" )
+ && method . getParameterTypes (). length == 1
+ && param . equals ( method . getParameterTypes ()[ 0 ]))
+ return method ;
+ }
+ }
+
+ return null ;
+ }
writeReplace方法提供了一种扩展序列化的方式,对与Dubbo暂不支持的类型,可以通过这个扩展点,返回一个自定义的类型的对象,替代原本需要序列化的对象,然后对自定义的对象进行序列化。
但是有个问题,如果producer通过writeReplace方法自定义一个对象来序列化,consumer反序列化时,怎么把自定义的对象转回原本类型呢?这里不展开讲,但是办法肯定是有的,答案就是 readResolve方法 。: )
3.5.2 FieldSerializer 对于没有writeReplace方法的类型,就继续走hessian2设定的序列化逻辑。从JavaSerializer#writeInstance(Object, AbstractHessianOutput)
继续。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+
// JavaSerializer.java
+ public void writeInstance ( Object obj , AbstractHessianOutput out ) throws IOException {
+ for ( int i = 0 ; i < _fields . length ; i ++) {
+ Field field = _fields [ i ];
+ // _fieldSerializers是数组 FieldSerializer[]
+ // 是在JavaSerializer构造方法中,根据序列化类中各个字段类型,依次创建FieldSerializer(或子类)的实例存入
+ _fieldSerializers [ i ]. serialize ( out , obj , field );
+ }
+ }
+
+ // JavaSerializer构造方法中调用
+ // 根据序列化类中各个字段类型,返回对应类型的FieldSerializer(或子类)实例
+ private static FieldSerializer getFieldSerializer ( Class type )
+{
+ if ( int . class . equals ( type )
+ || byte . class . equals ( type )
+ || short . class . equals ( type )
+ || int . class . equals ( type )) {
+ return IntFieldSerializer . SER ;
+ }
+ else if ( long . class . equals ( type )) {
+ return LongFieldSerializer . SER ;
+ }
+ else if ( double . class . equals ( type ) ||
+ float . class . equals ( type )) {
+ return DoubleFieldSerializer . SER ;
+ }
+ else if ( boolean . class . equals ( type )) {
+ return BooleanFieldSerializer . SER ;
+ }
+ else if ( String . class . equals ( type )) {
+ return StringFieldSerializer . SER ;
+ }
+ else if ( java . util . Date . class . equals ( type )
+ || java . sql . Date . class . equals ( type )
+ || java . sql . Timestamp . class . equals ( type )
+ || java . sql . Time . class . equals ( type )) {
+ return DateFieldSerializer . SER ;
+ }
+ else
+ return FieldSerializer . SER ;
+ }
在JavaSerializer#writeObject(Object, AbstractHessianOutput)
方法中,写完类型信息、类型字段长度、各字段名称后,就会通过writeInstance方法依次序列化类型中的各字段。字段的序列化,就是由静态类FieldSerializer类完成。
比如DemoDTO中的String类型字段,就会有FieldSerializer的子类StringFieldSerializer,完成String consumer
字段序列化。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+
// JavaSerializer.java
+ static class StringFieldSerializer extends FieldSerializer {
+ static final FieldSerializer SER = new StringFieldSerializer ();
+
+ void serialize ( AbstractHessianOutput out , Object obj , Field field ) throws IOException {
+ String value = null ;
+ try {
+ value = ( String ) field . get ( obj );
+ } catch ( IllegalAccessException e ) {
+ log . log ( Level . FINE , e . toString (), e );
+ }
+ // 调用Hessian2Output类的writeString方法,值写入输出流
+ out . writeString ( value );
+ }
+ }
但是DemoDTO中的Instant类型字段,在JavaSerializer#getFieldSerializer(Class)
方法中没有对应类型的,得到的是默认的FieldSerializer实例。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+
static class FieldSerializer {
+ static final FieldSerializer SER = new FieldSerializer ();
+
+ void serialize ( AbstractHessianOutput out , Object obj , Field field ) throws IOException {
+ Object value = null ;
+
+ try {
+ value = field . get ( obj );
+ } catch ( IllegalAccessException e ) {
+ log . log ( Level . FINE , e . toString (), e );
+ }
+
+ try {
+ // 又回到 Hessian2Output#writeObject(Object)方法
+ // 再次获得序列化工厂类Hessian2SerializerFactory,获得一个Serializer对象,又通过Serializer对象序列化
+ // 这里有递归的感觉了
+ out . writeObject ( value );
+ } catch ( RuntimeException e ) {
+ throw new RuntimeException ( e . getMessage () + "\n Java field: " + field , e );
+ } catch ( IOException e ) {
+ throw new IOExceptionWrapper ( e . getMessage () + "\n Java field: " + field , e );
+ }
+ }
+ }
综上所述,Hessian2序列化的核心的几个类是:Hessian2Output、SerializerFactory(Hessian2SerializerFactory) 、JavaSerializer和FieldSerializer。
整体思路是:
Hessian2Output#writeObject(Object)
:对象 序列化的入口SerializerFactory#getSerializer(Class)
:根据对象类型,找到对应类型的Serializer完成对象序列化,无对应类型的交给JavaSerializerJavaSerializer#getFieldSerializer(Class)
:完成对象类型信息的序列化,然后依次序列化各个字段,为各字段找到对应类型的FieldSerializer子类完成字段序列化,无对应类型的字段交给FieldSerializer。FieldSerializer#serialize(AbstractHessianOutput, Object, Field)
:FieldSerializer会把字段值再当做一个对象,再走一遍整个对象序列化过程。这个设计想法是蛮好的,简单对象直接有对应类序列化,没有对应序列化方法的把对象的序列化拆解成各个字段的序列化。在理想的情况下,所有对象都可以通过不断的拆解,形成简单的对象,然后有相应的Serializer或FieldSerializer完成序列化 。就好像像复杂问题拆解成数个简单问题,也有点像庖丁解牛的感觉。
3.6 序列化调用栈 了解dubbo在hessian2序列化方式时的逻辑之后,再来看本文开头的异常。上述大段文字内容繁多,很难串联起来,我们根据hessian2的序列化逻辑,整理整个序列化过程的调用栈,当然也可以直接打断点看
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+
ExchangeCodec # encode
+ -> ExchangeCodec # encodeResponseData
+ -> DubboCodec # encodeResponseData
+ -> Hessian2ObjectOutput # writeObject
+ // DemoDTO类型开始序列化
+ -> Hessian2Output # writeObject
+ -> SerializerFactory # getSerializer
+ -> JavaSerializer # writeObject
+ -> JavaSerializer # writeInstance
+ -> FieldSerializer # serialize
+ // Instant类型开始序列化
+ -> Hessian2Output # writeObject
+ -> SerializerFactory # getSerializer
+ -> JavaSerializer # writeObject
+ // Ser类型开始序列化(Instant的writeReplace方法产生的替代对象)
+ -> Hessian2Output # writeObject
+ -> SerializerFactory # getSerializer
+ -> JavaSerializer # writeObject
+ -> JavaSerializer # writeInstance
+ -> FieldSerializer # serialize
+ // Instant类型开始序列化(Ser类中的object字段)
+ -> Hessian2Output # writeObject
+ -> SerializerFactory # getSerializer
+ -> JavaSerializer # writeObject
+ // Ser(Instant的writeReplace方法产生的替代类)
+ -> Hessian2Output # writeObject
+ -> ......
通过调用栈,是不是一眼就发现问题所在了?这个调用栈倒序来看,可以发现正好开头是异常栈的顺序。
4 结论 在Dubbo反序列化的过程中,产生的栈溢出的原因是:
Dubbo(v2.5.3)没有相应的Serializer或FieldSerializer能处理Instant数据类型,并且Instant正好有writeReplace方法,返回的替代类中的object字段又包含Instant本身。导致需要序列化的对象在Instant、Ser之间反复横跳,不断调用序列化方法逻辑,最终方法栈满,导致栈溢出错误。
结合网上信息,可以发现不只是Instant类,在JDK8新增的时间API,如LocalDateTime、Period等,都会和Instant有一样的问题,也是一样的原因。
5 解决方案 知道了问题的原因之后,就可以对症下药了。本人水平有限,暂时想到以下几种方案:
5.1 使用Date类型 首先最简单的方式,就是不使用JDK8中引入的时间API,改为使用Date。
这种方式没有直接解决问题,而是有些逃避问题了。
5.1 writeReplace / readResolve 方法 修改Instant的writeReplace方法可能不是很方便,我们可以为DemoDTO添加writeReplace方法。总体思路的话就是:provider把Instant拆解成支持的数据类型,到了consumer再转化成Instant。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+
public class DemoDTO implements Serializable {
+
+ private static final long serialVersionUID = - 311647434535770294L ;
+
+ private String consumer ;
+ private Instant instant ;
+
+
+ // 序列化时JavaSerializer会调用此方法,然后序列化替代类对象repl
+ private DemoDTOHandle writeReplace () {
+ System . out . println ( "call writeReplace" );
+ DemoDTOHandle repl = new DemoDTOHandle ();
+ repl . setConsumer ( consumer );
+ if ( instant != null ) {
+ repl . setSeconds ( instant . getEpochSecond ());
+ repl . setNanos ( instant . getNano ());
+ }
+ return repl ;
+ }
+
+ // getter,setter略...
+ }
DemoDTOHandle中Instant拆成seconds、nanos字段存储,然后会代替DemoDTO进行序列化传输。代码如下。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+
public class DemoDTOHandle implements HessianHandle , Serializable {
+
+ private static final long serialVersionUID = - 8532997896892606136L ;
+
+ private String consumer ;
+
+ private long seconds ;
+ private int nanos ;
+
+ // 反序列化时,JavaDeserializer会调用此方法,重新组装实际的类
+ private DemoDTO readResolve () {
+ System . out . println ( "call readResolve" );
+ DemoDTO dto = new DemoDTO ();
+ dto . setConsumer ( consumer );
+ dto . setInstant ( Instant . ofEpochSecond ( seconds , nanos ));
+ return dto ;
+ }
+
+ // getter,setter略...
+ }
DemoDTOHandle为什么要实现HessianHandle接口呢?答案在SerializerFactory#getObjectDeserializer(String, Class)
中,如果不是HessianHandle实现类的话,是得不到DemoDTOHandle相关的Deserializer,只有DemoDTO相关的Deserializer,那么就无法使用DemoDTOHandle中的readResolve方法转换回对象了。
5.2 修改序列化方式 之前提到,使用的是dubbo协议、hessian2序列化方式。事实上dubbo协议同样可以使用其他多种序列化方式。下面列举的序列化方式,都可以支持JDK8时间API的序列化处理。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+
<dubbo:protocol serialization= "java" />
+
+ <dubbo:protocol serialization= "compactedjava" />
+
+ <dubbo:protocol serialization= "nativejava" />
+
+ <dubbo:protocol serialization= "json" />
+
+ <dubbo:protocol serialization= "fastjson" />
当然如果升级了Dubbo版本的话,会支持更多的序列化方式。
5.3 升级Dubbo版本(√) 当然,使用的Dubbo版本是2.5.3,实在是太老了。搜索maven仓库,看到2.5.3版本更新时间是2012年,十年前。
可以选择升级到2.6.x版本 。会发现2.6.x版本在序列化方面也做了一些优化。新版本不仅包含了新功能,扩展性也有所提升。但是需要注意,如果是升级2.6.6+版本,需要关注netty版本的变化。
2.6.x 支持JDK8时间API 在2.6.x版本中,在SerializerFactory 中增加了对JDK8时间API的处理,加入了Java8TimeSerializer以及一系列的HessianHandle。意味着2.6.x版本Dubbo将不再有本文开头遇到的问题。
2.6.x 支持扩展SerializerFactory 同时SerializerFactory 也提供了一定的扩展性,支持添加你想要的SerializerFactory。如果还有目标序列化方式无法处理的类型,可以自定义SerializerFactory、Serializer,只要将自定义SerializerFactory添加到当前序列化方式的SerializerFactory 中,就可以轻松扩展。在2.5.x上是不支持这种扩展的 。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+
// SerializerFactory.java 2.6.x
+ public class SerializerFactory extends AbstractSerializerFactory {
+ // 略...
+
+ protected ArrayList _factories = new ArrayList ();
+ // 略...
+
+ public void addFactory ( AbstractSerializerFactory factory ) {
+ _factories . add ( factory );
+ }
+
+ public Serializer getSerializer ( Class cl ) throws HessianProtocolException {
+ Serializer serializer ;
+ serializer = ( Serializer ) _staticSerializerMap . get ( cl );
+ if ( serializer != null )
+ return serializer ;
+
+ if ( _cachedSerializerMap != null ) {
+ synchronized ( _cachedSerializerMap ) {
+ serializer = ( Serializer ) _cachedSerializerMap . get ( cl );
+ }
+ if ( serializer != null )
+ return serializer ;
+ }
+
+ // 遍历扩展的SerializerFactory。尝试由扩展的SerializerFactory提供Serializer
+ // 2.5.3没有这个功能
+ for ( int i = 0 ; serializer == null && _factories != null && i < _factories . size (); i ++) {
+ AbstractSerializerFactory factory ;
+ factory = ( AbstractSerializerFactory ) _factories . get ( i );
+ serializer = factory . getSerializer ( cl );
+ }
+
+ if ( serializer != null ) {
+ }
+
+ else if ( JavaSerializer . getWriteReplace ( cl ) != null )
+ serializer = new JavaSerializer ( cl , _loader );
+ else if ( HessianRemoteObject . class . isAssignableFrom ( cl ))
+ serializer = new RemoteSerializer ();
+ // else if (BurlapRemoteObject.class.isAssignableFrom(cl))
+ // serializer = new RemoteSerializer();
+ else if ( Map . class . isAssignableFrom ( cl )) {
+ if ( _mapSerializer == null )
+ _mapSerializer = new MapSerializer ();
+ serializer = _mapSerializer ;
+ }
+ else if ( Collection . class . isAssignableFrom ( cl )) {
+ if ( _collectionSerializer == null ) {
+ _collectionSerializer = new CollectionSerializer ();
+ }
+ serializer = _collectionSerializer ;
+ }
+ else if ( cl . isArray ())
+ serializer = new ArraySerializer ();
+ else if ( Throwable . class . isAssignableFrom ( cl ))
+ serializer = new ThrowableSerializer ( cl , getClassLoader ());
+ else if ( InputStream . class . isAssignableFrom ( cl ))
+ serializer = new InputStreamSerializer ();
+ else if ( Iterator . class . isAssignableFrom ( cl ))
+ serializer = IteratorSerializer . create ();
+
+ else if ( Enumeration . class . isAssignableFrom ( cl ))
+ serializer = EnumerationSerializer . create ();
+ else if ( Calendar . class . isAssignableFrom ( cl ))
+ serializer = CalendarSerializer . create ();
+ else if ( Locale . class . isAssignableFrom ( cl ))
+ serializer = LocaleSerializer . create ();
+ else if ( Enum . class . isAssignableFrom ( cl ))
+ serializer = new EnumSerializer ( cl );
+ if ( serializer == null )
+ serializer = getDefaultSerializer ( cl );
+ if ( _cachedSerializerMap == null )
+ _cachedSerializerMap = new HashMap ( 8 );
+
+ synchronized ( _cachedSerializerMap ) {
+ _cachedSerializerMap . put ( cl , serializer );
+ }
+ return serializer ;
+ }
+ }
5.4 SPI扩展Serialization 这个方法理论上是完全可行的,但是有些过于大动干戈了。总体思路是:借助Dubbo的SPI机制,对Serialization进行扩展,基于hessian2的SerializerFactory ,对Serializer、Deserializer进行扩展。
6 写在最后 最开始计划用一两天的时间写完这篇博客,但是过程中又不断的遇到问题、解决问题,最终花了整个五一假期才完成。通过这个问题,算是对Dubbo框架的序列化这块有了一些了解了。
\ No newline at end of file
diff --git a/posts/dubbo-stack-overflow-due-to-jdk8-instant/index.md b/posts/dubbo-stack-overflow-due-to-jdk8-instant/index.md
new file mode 100644
index 00000000..8a6c38c0
--- /dev/null
+++ b/posts/dubbo-stack-overflow-due-to-jdk8-instant/index.md
@@ -0,0 +1,887 @@
+# JDK8日期时间API导致Dubbo调用StackOverflow错误
+
+
+
+
+
+
+## 0 前言
+
+博客搭建之后,不知道从何写起。思来想去,决定从记录一些工作中遇到的问题开始。一来对问题有个归档,算是有一些积累,方便日后回顾;二来促使自己遇到问题时能探本朔源,做到知其然知其所以然,不要草草百度了事。不积跬步,无以至千里;不积小流,无以成江海。
+
+
+
+## 1 问题
+
+最近项目中使用dubbo(**v2.5.3**)的时候,遇到一个问题:dubbo服务方在对response编码时失败,发生了栈溢出异常。可以通过一个demo([dubbo-stack-overflow-due-to-jkd8-instant](https://github.com/dingyufan/blog-demo-all/tree/main/dubbo-stack-overflow-due-to-jkd8-instant))复现这个异常。
+
+![image-20220421171300795](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/image-20220421171300795.png)
+
+看到栈溢出,一般想到的情况要么是递归过深,或是循环调用方法次数过多。为什么会在dubbo序列化编码的时候发生这个异常呢?通过查看源码提交记录,结合异常信息Fail to encode repsonse,并且在异常栈中出现JavaSerializer类,判断**问题出在Dubbo在对Response序列化时无法处理Instant类型对象**。
+
+网上搜索到的博客基本上只是说了有这样的问题,但是没有说明具体的原因,都是草草改用Date了事。通过这篇博客,记录一下Instant类型对象引起Dubbo对Response序列化失败导致栈溢出的根本原因以及解决方案。
+
+
+
+## 3 Dubbo序列化Response
+
+dubbo对不同协议、不同序列化方式处理方式是不一样的。此项目使用默认协议:**dubbo协议**,同时也采用dubbo协议默认序列化方式**:hessian2**。下文的序列化过程分析就是基于此的。
+
+```xml
+
+
+```
+
+**参考官网文档 [2.4 服务提供方返回调用结果](https://dubbo.apache.org/zh/docs/v2.7/dev/source/service-invoking-process/#24-%E6%9C%8D%E5%8A%A1%E6%8F%90%E4%BE%9B%E6%96%B9%E8%BF%94%E5%9B%9E%E8%B0%83%E7%94%A8%E7%BB%93%E6%9E%9C) **,我们从Dubbo处理返回结果入手。
+
+
+
+### 3.1 ExchangeCodec / DubboCodec
+
+ExchangeCodec是Dubbo **exchange信息交换层**的编码器。`ExchangeCodec#encode(Channel, ChannelBuffer, Object)`方法是Dubbo对Request和Response进行编码的入口,逻辑很简单,主要是根据消息的类型,交由对应方法进行编码,对于不是自己负责的非Request、Response类型的消息对象,交给父类处理。
+
+```java
+// ExchangeCodec.java
+public void encode(Channel channel, ChannelBuffer buffer, Object msg) throws IOException {
+ if (msg instanceof Request) {
+ encodeRequest(channel, buffer, (Request) msg);
+ } else if (msg instanceof Response) {
+ // 对response对象进行编码
+ encodeResponse(channel, buffer, (Response) msg);
+ } else {
+ super.encode(channel, buffer, msg);
+ }
+}
+```
+
+`ExchangeCodec#encodeResponse(Channel, ChannelBuffer, Response)`方法主要做的就是组装消息写入ChannelBuffer,这样消息就可以发送给调用方。消息两部分组成:**消息头**,包含状态序列化等相关信息的字节数组;**消息体**:Response返回的具体数据转换成的字节数组。
+
+```java
+// ExchangeCodec.java
+protected void encodeResponse(Channel channel, ChannelBuffer buffer, Response res) throws IOException {
+ try {
+ // 获取序列化方式*
+ Serialization serialization = getSerialization(channel);
+ // 消息头字节数组。长度16
+ byte[] header = new byte[HEADER_LENGTH];
+ // 消息头 写入魔数
+ Bytes.short2bytes(MAGIC, header);
+ // 消息头 写入序列化器ID信息
+ header[2] = serialization.getContentTypeId();
+ if (res.isHeartbeat()) header[2] |= FLAG_EVENT;
+ byte status = res.getStatus();
+ // 消息头 写入状态
+ header[3] = status;
+ // 消息头 写入
+ Bytes.long2bytes(res.getId(), header, 4);
+
+ int savedWriteIndex = buffer.writerIndex();
+ buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
+ ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
+ // 得到序列化器*
+ ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
+ if (status == Response.OK) {
+ if (res.isHeartbeat()) {
+ encodeHeartbeatData(channel, out, res.getResult());
+ } else {
+ // 对调用结果进行序列化*
+ // 这个方法会调用子类DubboCodec的override方法实现
+ encodeResponseData(channel, out, res.getResult());
+ }
+ }
+ else out.writeUTF(res.getErrorMessage());
+ out.flushBuffer();
+ bos.flush();
+ bos.close();
+ // 消息体字节长度
+ int len = bos.writtenBytes();
+ checkPayload(channel, len);
+ Bytes.int2bytes(len, header, 12);
+ // 先写完消息体,再写消息头,再设置writeIndex位置
+ buffer.writerIndex(savedWriteIndex);
+ buffer.writeBytes(header);
+ buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
+ } catch (Throwable t) {
+ // 略...
+ }
+}
+```
+
+在这里先不关注消息头、ChannelBuffer等操作,这次主要看序列化相关的几个步骤:
+
+#### 3.1.1 获取序列化方式
+
+这里实际是父类中的方法`AbstractCodec#getSerialization(Channel)`方法。这一步是利用Dubbo SPI机制,实例化一个序列化方式对象。具体过程不是本次的重点,只要理解这一步会根据``配置的序列化方式返回对应的序列化方式对象。比如这里得到的就是Hessian2Serialization对象。
+
+#### 3.1.2 得到序列化器
+
+这里调用`Serialization#serialize(URL, OutputStream)`方法,得到一个序列化器。hessian2序列化提供的序列化器是Hessian2ObjectOutput对象,通过构造函数传入了`ExchangeCodec#encodeResponse(Channel, ChannelBuffer, Response)`方法中定义的包装了buffer的bos。这样Hessian2ObjectOutput对象可以将具体数据写入到buffer中。
+
+```java
+public class Hessian2Serialization implements Serialization {
+
+ public static final byte ID = 2;
+
+ public byte getContentTypeId() {
+ return ID;
+ }
+
+ public String getContentType() {
+ return "x-application/hessian2";
+ }
+
+ public ObjectOutput serialize(URL url, OutputStream out) throws IOException {
+ // 序列化器对象
+ return new Hessian2ObjectOutput(out);
+ }
+
+ public ObjectInput deserialize(URL url, InputStream is) throws IOException {
+ return new Hessian2ObjectInput(is);
+ }
+}
+```
+
+#### 3.1.3 对调用结果进行序列化
+
+配置中指定的是dubbo协议,这里实际会被调用的是`DubboCodec#encodeResponseData(Channel, ObjectOutput, Object)`重写方法。DubboCodec继承自ExchangeCodec,是Dubbo **protocol远程调用层**的编码器,主要负责实现协议层面的Response数据编码。
+
+```java
+// DubboCodec.java
+@Override
+protected void encodeResponseData(Channel channel, ObjectOutput out, Object data) throws IOException {
+ Result result = (Result) data;
+
+ Throwable th = result.getException();
+ if (th == null) {
+ Object ret = result.getValue();
+ if (ret == null) {
+ out.writeByte(RESPONSE_NULL_VALUE);
+ } else {
+ // 序列化器中写入response数据标志位
+ out.writeByte(RESPONSE_VALUE);
+ // 序列化器中写入正常返回的对象
+ out.writeObject(ret);
+ }
+ } else {
+ out.writeByte(RESPONSE_WITH_EXCEPTION);
+ out.writeObject(th);
+ }
+}
+```
+
+
+
+### 3.2 Hessian2ObjectOutput
+
+在3.1.2中提到过,得到的序列化器是Hessian2ObjectOutput,实现了ObjectOutput接口。对于常见的简单数据对象,ObjectOutput接口中都定义了相应处理方法,同时还定义writeObject(Object)方法处理复杂对象的序列化。
+
+Hessian2ObjectOutput实现了接口定义的各个方法,通过一个Hessian2Output对象,完成各种数据类型hessian2序列化的具体操作。
+
+```java
+public class Hessian2ObjectOutput implements ObjectOutput {
+ private final Hessian2Output mH2o;
+
+ public Hessian2ObjectOutput(OutputStream os) {
+ mH2o = new Hessian2Output(os);
+ mH2o.setSerializerFactory(Hessian2SerializerFactory.SERIALIZER_FACTORY);
+ }
+
+ public void writeBool(boolean v) throws IOException {
+ mH2o.writeBoolean(v);
+ }
+ // 略...
+
+ public void writeObject(Object obj) throws IOException {
+ mH2o.writeObject(obj);
+ }
+
+ public void flushBuffer() throws IOException {
+ mH2o.flushBuffer();
+ }
+}
+```
+
+**`Hessian2ObjectOutput#writeObject(Object)`方法是response数据对象序列化的入口**,数据对象序列化的操作从这一步正式开始。这个方法中调用的是`Hessian2Output#writeObject(Object)`方法。
+
+
+
+### 3.3 Hessian2Output
+
+注意Hessian2Output的方法,如`writeBoolean(boolean)`,对值进行编码写入的时候,没有直接写入` _os`,而是维护一个字节数组 `_buffer`和 `_offset`值。回头看3.1中的`ExchangeCodec#encodeResponse(Channel, ChannelBuffer, Response)`方法,会调用`out.flushBuffer()`,通过Hessian2ObjectOutput来调用`Hessian2Output#flushBuffer()`方法,将 `_buffer`内容写入 `_os` ,即写入 `bos`,即ChannelBuffer对象`buffer`。
+
+`Hessian2Output#writeObject(Object)`方法不像其他方法,没有直接向`_buffer`写入具体的字节,而是尝试获取一个Serializer对象,完成对象的序列化。
+
+```java
+public class Hessian2Output extends AbstractHessianOutput implements Hessian2Constants {
+ protected OutputStream _os;
+ private final byte []_buffer = new byte[SIZE];
+ // 略...
+
+ public Hessian2Output(OutputStream os) {
+ _os = os;
+ }
+ // 略...
+
+ public void writeBoolean(boolean value) throws IOException {
+ if (SIZE < _offset + 16)
+ flush();
+
+ if (value)
+ // 不直接写入_os
+ _buffer[_offset++] = (byte) 'T';
+ else
+ _buffer[_offset++] = (byte) 'F';
+ }
+
+ public void writeObject(Object object) throws IOException {
+ if (object == null) {
+ writeNull();
+ return;
+ }
+
+ Serializer serializer;
+ // 获得序列化工厂类,获得一个Serializer对象
+ serializer = findSerializerFactory().getSerializer(object.getClass());
+ // 完成对象的序列化
+ serializer.writeObject(object, this);
+ }
+
+ public final void flushBuffer() throws IOException {
+ int offset = _offset;
+ if (! _isStreaming && offset > 0) {
+ _offset = 0;
+ _os.write(_buffer, 0, offset);
+ } else if (_isStreaming && offset > 3) {
+ int len = offset - 3;
+ _buffer[0] = 'p';
+ _buffer[1] = (byte) (len >> 8);
+ _buffer[2] = (byte) len;
+ _offset = 3;
+
+ _os.write(_buffer, 0, offset);
+ }
+ }
+}
+
+```
+
+findSerializerFactory()会从父类获得一个SerializerFactory对象,回头从3.2看到,在Hessian2ObjectOutput中,为Hessian2Output指定SerializerFactory 具体对象为`Hessian2SerializerFactory.SERIALIZER_FACTORY`。我们主要来看getSerializer(object.getClass())
+
+
+
+### 3.4 SerializerFactory / Hessian2SerializerFactory
+
+Hessian2SerializerFactory相对比较简单,只是实现了getClassLoader() 方法,然后在公共静态常量量中构造了一个Hessian2SerializerFactory对象供使用。
+
+```java
+public class Hessian2SerializerFactory extends SerializerFactory {
+
+ public static final SerializerFactory SERIALIZER_FACTORY = new Hessian2SerializerFactory();
+
+ private Hessian2SerializerFactory() {
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return Thread.currentThread().getContextClassLoader();
+ }
+
+}
+```
+
+他父类SerializerFactory功能更加丰富。`SerializerFactory#getSerializer(Class)`方法比较长,但是其实整体逻辑并不复杂:**根据类型Class返回相应的Serializer实例,如果类型和方法中列举的都不匹配,则会返回一个默认的Serializer对象,即 JavaSerializer对象**。
+
+```java
+public class SerializerFactory extends AbstractSerializerFactory {
+ // 略...
+
+ public Serializer getSerializer(Class cl) throws HessianProtocolException {
+ Serializer serializer;
+ serializer = (Serializer) _staticSerializerMap.get(cl);
+
+ if (serializer != null)
+ return serializer;
+
+ if (_cachedSerializerMap != null) {
+ synchronized (_cachedSerializerMap) {
+ serializer = (Serializer) _cachedSerializerMap.get(cl);
+ }
+ if (serializer != null)
+ return serializer;
+ }
+
+ for (int i = 0; serializer == null && _factories != null && i < _factories.size(); i++) {
+ AbstractSerializerFactory factory;
+ factory = (AbstractSerializerFactory) _factories.get(i);
+ serializer = factory.getSerializer(cl);
+ }
+
+ if (serializer != null) {
+
+ } else if (JavaSerializer.getWriteReplace(cl) != null)
+ serializer = new JavaSerializer(cl, _loader);
+ else if (HessianRemoteObject.class.isAssignableFrom(cl))
+ serializer = new RemoteSerializer();
+// else if (BurlapRemoteObject.class.isAssignableFrom(cl))
+// serializer = new RemoteSerializer();
+ else if (Map.class.isAssignableFrom(cl)) {
+ if (_mapSerializer == null)
+ _mapSerializer = new MapSerializer();
+ serializer = _mapSerializer;
+ }
+ else if (Collection.class.isAssignableFrom(cl)) {
+ if (_collectionSerializer == null) {
+ _collectionSerializer = new CollectionSerializer();
+ }
+ serializer = _collectionSerializer;
+ }
+ else if (cl.isArray())
+ serializer = new ArraySerializer();
+ else if (Throwable.class.isAssignableFrom(cl))
+ serializer = new ThrowableSerializer(cl, getClassLoader());
+ else if (InputStream.class.isAssignableFrom(cl))
+ serializer = new InputStreamSerializer();
+ else if (Iterator.class.isAssignableFrom(cl))
+ serializer = IteratorSerializer.create();
+ else if (Enumeration.class.isAssignableFrom(cl))
+ serializer = EnumerationSerializer.create();
+ else if (Calendar.class.isAssignableFrom(cl))
+ serializer = CalendarSerializer.create();
+ else if (Locale.class.isAssignableFrom(cl))
+ serializer = LocaleSerializer.create();
+ else if (Enum.class.isAssignableFrom(cl))
+ serializer = new EnumSerializer(cl);
+
+ if (serializer == null)
+ // 都不符合以上类似,则返回默认的serializer
+ serializer = getDefaultSerializer(cl);
+
+ if (_cachedSerializerMap == null)
+ _cachedSerializerMap = new HashMap(8);
+
+ synchronized (_cachedSerializerMap) {
+ // 缓存这个类型对应的serializer,下次直接从缓存获取
+ _cachedSerializerMap.put(cl, serializer);
+ }
+
+ return serializer;
+ }
+
+ protected Serializer getDefaultSerializer(Class cl) {
+ if (_defaultSerializer != null)
+ return _defaultSerializer;
+ if (!Serializable.class.isAssignableFrom(cl) && ! _isAllowNonSerializable) {
+ throw new IllegalStateException("Serialized class " + cl.getName() + " must implement java.io.Serializable");
+ }
+
+ return new JavaSerializer(cl, _loader);
+ }
+}
+```
+
+对于demo中的返回的数据对象DemoDTO,很显然就是会得到一个JavaSerializer,通过它的完成序列化。
+
+
+
+### 3.5 JavaSerializer
+
+回顾上文3.3节中,serializer对象完成序列哈的入库也是writeObject()方法。来看`JavaSerializer#writeObject(Object, AbstractHessianOutput)`方法。这里面序列化分两种情况:一是要序列化的对象有writeReplce()方法的,直接调用writeReplce()方法完成序列化;而是常规的使用静态类FieldSerializer序列化。
+
+```java
+// JavaSerializer.java
+public void writeObject(Object obj, AbstractHessianOutput out) throws IOException {
+ if (out.addRef(obj)) {
+ return;
+ }
+
+ Class cl = obj.getClass();
+ try {
+ // 调用序列化对象的writeReplace方法完成序列化
+ if (_writeReplace != null) {
+ Object repl;
+ if (_writeReplaceFactory != null)
+ repl = _writeReplace.invoke(_writeReplaceFactory, obj);
+ else
+ repl = _writeReplace.invoke(obj);
+
+ out.removeRef(obj);
+ out.writeObject(repl);
+ out.replaceRef(repl, obj);
+ return;
+ }
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ // log.log(Level.FINE, e.toString(), e);
+ throw new RuntimeException(e);
+ }
+
+ // hessian2会写入对象开始标记;hessian会写map开始标记并返回-2
+ int ref = out.writeObjectBegin(cl.getName());
+
+ if (ref < -1) {
+ // 这种情况看起来是兼容hessian序列化的,而不是hessian2序列化的
+ // hessian序列化是把对象当做map处理,所以writeObject10()方法里会循环写入字段名称然后马上字段序列化,最后还会写一个map结束标志
+ writeObject10(obj, out);
+ }
+ else {
+ if (ref == -1) {
+ // 写入对象包含字段长度,以及各个类型字段信息
+ writeDefinition20(out);
+ // 类型信息编码处理
+ out.writeObjectBegin(cl.getName());
+ }
+ // 这里会开始会遍历序列化对象包含的各个字段
+ writeInstance(obj, out);
+ }
+}
+```
+
+
+
+#### 3.5.1 writeReplace方法
+
+JavaSerializer 使用一个Method变量 `_writeReplace`保存writeReplace()方法,这个方法是从那里来的呢?在JavaSerializer构造方法中,会调用一个私有方法`introspectWriteReplace(Class, ClassLoader)`。它会从两个地方找writeReplace()方法:**一是序列化类相应的HessianSerializer类;二是序列化类本身**。但是要注意,HessianSerializer类的writeReplace方法签名 和 序列化类本身writeReplace方法签名 是有区别的。
+
+```java
+// JavaSerializer.java
+private void introspectWriteReplace(Class cl, ClassLoader loader) {
+ try {
+ String className = cl.getName() + "HessianSerializer";
+ // 加载 序列化类类名+HessianSerializer的类。例如demo中的DemoDTO类,则会
+ // 尝试加载cn.dingyufan.blog.demo.dubbostackoverflowduetojkd8instantprovider.api.dto.DemoDTOHessianSerializer类
+ Class serializerClass = Class.forName(className, false, loader);
+ // 实例化相应的HessianSerializer类型
+ Object serializerObject = serializerClass.newInstance();
+ // 反射获取WriteReplace方法
+ Method writeReplace = getWriteReplace(serializerClass, cl);
+
+ if (writeReplace != null) {
+ _writeReplaceFactory = serializerObject;
+ _writeReplace = writeReplace;
+ return;
+ }
+ } catch (ClassNotFoundException e) {
+ // 没有相应HessianSerializer类就会抛出ClassNotFoundException,这个异常不处理,后面会再次尝试从序列化类本身寻找
+ } catch (Exception e) {
+ log.log(Level.FINER, e.toString(), e);
+ }
+ // 从序列化类本身找writeReplace方法
+ _writeReplace = getWriteReplace(cl);
+}
+
+// 从要序列化的类型寻找 方法名为writeReplace、入参为空 的方法
+protected static Method getWriteReplace(Class cl) {
+ for (; cl != null; cl = cl.getSuperclass()) {
+ Method []methods = cl.getDeclaredMethods();
+
+ for (int i = 0; i < methods.length; i++) {
+ Method method = methods[i];
+
+ if (method.getName().equals("writeReplace") &&
+ method.getParameterTypes().length == 0)
+ return method;
+ }
+ }
+
+ return null;
+}
+
+// 从相应HessianSerializer类找到 方法名为writeReplace、 入参有且仅为要序列化类型 的方法
+protected Method getWriteReplace(Class cl, Class param)
+{
+ for (; cl != null; cl = cl.getSuperclass()) {
+ for (Method method : cl.getDeclaredMethods()) {
+ if (method.getName().equals("writeReplace")
+ && method.getParameterTypes().length == 1
+ && param.equals(method.getParameterTypes()[0]))
+ return method;
+ }
+ }
+
+ return null;
+}
+```
+
+writeReplace方法提供了一种扩展序列化的方式,对与Dubbo暂不支持的类型,可以通过这个扩展点,返回一个自定义的类型的对象,替代原本需要序列化的对象,然后对自定义的对象进行序列化。
+
+但是有个问题,**如果producer通过writeReplace方法自定义一个对象来序列化,consumer反序列化时,怎么把自定义的对象转回原本类型呢?**这里不展开讲,但是办法肯定是有的,答案就是**readResolve方法** 。: )
+
+
+
+#### 3.5.2 FieldSerializer
+
+对于没有writeReplace方法的类型,就继续走hessian2设定的序列化逻辑。从`JavaSerializer#writeInstance(Object, AbstractHessianOutput)`继续。
+
+```java
+// JavaSerializer.java
+public void writeInstance(Object obj, AbstractHessianOutput out) throws IOException {
+ for (int i = 0; i < _fields.length; i++) {
+ Field field = _fields[i];
+ // _fieldSerializers是数组 FieldSerializer[]
+ // 是在JavaSerializer构造方法中,根据序列化类中各个字段类型,依次创建FieldSerializer(或子类)的实例存入
+ _fieldSerializers[i].serialize(out, obj, field);
+ }
+}
+
+// JavaSerializer构造方法中调用
+// 根据序列化类中各个字段类型,返回对应类型的FieldSerializer(或子类)实例
+private static FieldSerializer getFieldSerializer(Class type)
+{
+ if (int.class.equals(type)
+ || byte.class.equals(type)
+ || short.class.equals(type)
+ || int.class.equals(type)) {
+ return IntFieldSerializer.SER;
+ }
+ else if (long.class.equals(type)) {
+ return LongFieldSerializer.SER;
+ }
+ else if (double.class.equals(type) ||
+ float.class.equals(type)) {
+ return DoubleFieldSerializer.SER;
+ }
+ else if (boolean.class.equals(type)) {
+ return BooleanFieldSerializer.SER;
+ }
+ else if (String.class.equals(type)) {
+ return StringFieldSerializer.SER;
+ }
+ else if (java.util.Date.class.equals(type)
+ || java.sql.Date.class.equals(type)
+ || java.sql.Timestamp.class.equals(type)
+ || java.sql.Time.class.equals(type)) {
+ return DateFieldSerializer.SER;
+ }
+ else
+ return FieldSerializer.SER;
+}
+```
+
+在`JavaSerializer#writeObject(Object, AbstractHessianOutput)`方法中,写完类型信息、类型字段长度、各字段名称后,就会通过writeInstance方法依次序列化类型中的各字段。字段的序列化,就是由静态类FieldSerializer类完成。
+
+比如DemoDTO中的String类型字段,就会有FieldSerializer的子类StringFieldSerializer,完成`String consumer`字段序列化。
+
+```java
+// JavaSerializer.java
+static class StringFieldSerializer extends FieldSerializer {
+ static final FieldSerializer SER = new StringFieldSerializer();
+
+ void serialize(AbstractHessianOutput out, Object obj, Field field) throws IOException {
+ String value = null;
+ try {
+ value = (String) field.get(obj);
+ } catch (IllegalAccessException e) {
+ log.log(Level.FINE, e.toString(), e);
+ }
+ // 调用Hessian2Output类的writeString方法,值写入输出流
+ out.writeString(value);
+ }
+}
+```
+
+但是DemoDTO中的Instant类型字段,在`JavaSerializer#getFieldSerializer(Class)`方法中没有对应类型的,得到的是默认的FieldSerializer实例。
+
+```java
+static class FieldSerializer {
+ static final FieldSerializer SER = new FieldSerializer();
+
+ void serialize(AbstractHessianOutput out, Object obj, Field field) throws IOException {
+ Object value = null;
+
+ try {
+ value = field.get(obj);
+ } catch (IllegalAccessException e) {
+ log.log(Level.FINE, e.toString(), e);
+ }
+
+ try {
+ // 又回到 Hessian2Output#writeObject(Object)方法
+ // 再次获得序列化工厂类Hessian2SerializerFactory,获得一个Serializer对象,又通过Serializer对象序列化
+ // 这里有递归的感觉了
+ out.writeObject(value);
+ } catch (RuntimeException e) {
+ throw new RuntimeException(e.getMessage() + "\n Java field: " + field, e);
+ } catch (IOException e) {
+ throw new IOExceptionWrapper(e.getMessage() + "\n Java field: " + field, e);
+ }
+ }
+}
+```
+
+
+
+综上所述,Hessian2序列化的核心的几个类是:Hessian2Output、SerializerFactory(Hessian2SerializerFactory) 、JavaSerializer和FieldSerializer。
+
+整体思路是:
+
+- `Hessian2Output#writeObject(Object)`:对象 序列化的入口
+- `SerializerFactory#getSerializer(Class)`:根据对象类型,找到对应类型的Serializer完成对象序列化,无对应类型的交给JavaSerializer
+- `JavaSerializer#getFieldSerializer(Class)`:完成对象类型信息的序列化,然后依次序列化各个字段,为各字段找到对应类型的FieldSerializer子类完成字段序列化,无对应类型的字段交给FieldSerializer。
+- `FieldSerializer#serialize(AbstractHessianOutput, Object, Field)`:FieldSerializer会把字段值再当做一个对象,再走一遍整个对象序列化过程。
+
+这个设计想法是蛮好的,**简单对象直接有对应类序列化,没有对应序列化方法的把对象的序列化拆解成各个字段的序列化。在理想的情况下,所有对象都可以通过不断的拆解,形成简单的对象,然后有相应的Serializer或FieldSerializer完成序列化**。就好像像复杂问题拆解成数个简单问题,也有点像庖丁解牛的感觉。
+
+
+
+### 3.6 序列化调用栈
+
+了解dubbo在hessian2序列化方式时的逻辑之后,再来看本文开头的异常。上述大段文字内容繁多,很难串联起来,我们根据hessian2的序列化逻辑,整理整个序列化过程的调用栈,当然也可以直接打断点看
+
+```java
+ExchangeCodec#encode
+ ->ExchangeCodec#encodeResponseData
+ ->DubboCodec#encodeResponseData
+ ->Hessian2ObjectOutput#writeObject
+ // DemoDTO类型开始序列化
+ ->Hessian2Output#writeObject
+ ->SerializerFactory#getSerializer
+ ->JavaSerializer#writeObject
+ ->JavaSerializer#writeInstance
+ ->FieldSerializer#serialize
+ // Instant类型开始序列化
+ ->Hessian2Output#writeObject
+ ->SerializerFactory#getSerializer
+ ->JavaSerializer#writeObject
+ // Ser类型开始序列化(Instant的writeReplace方法产生的替代对象)
+ ->Hessian2Output#writeObject
+ ->SerializerFactory#getSerializer
+ ->JavaSerializer#writeObject
+ ->JavaSerializer#writeInstance
+ ->FieldSerializer#serialize
+ // Instant类型开始序列化(Ser类中的object字段)
+ ->Hessian2Output#writeObject
+ ->SerializerFactory#getSerializer
+ ->JavaSerializer#writeObject
+ // Ser(Instant的writeReplace方法产生的替代类)
+ ->Hessian2Output#writeObject
+ -> ......
+```
+
+通过调用栈,是不是一眼就发现问题所在了?这个调用栈倒序来看,可以发现正好开头是异常栈的顺序。
+
+
+
+## 4 结论
+
+在Dubbo反序列化的过程中,产生的栈溢出的原因是:
+
+**Dubbo(v2.5.3)没有相应的Serializer或FieldSerializer能处理Instant数据类型,并且Instant正好有writeReplace方法,返回的替代类中的object字段又包含Instant本身。导致需要序列化的对象在Instant、Ser之间反复横跳,不断调用序列化方法逻辑,最终方法栈满,导致栈溢出错误。**
+
+结合网上信息,可以发现不只是Instant类,在JDK8新增的时间API,如LocalDateTime、Period等,都会和Instant有一样的问题,也是一样的原因。
+
+
+
+## 5 解决方案
+
+知道了问题的原因之后,就可以对症下药了。本人水平有限,暂时想到以下几种方案:
+
+### 5.1 使用Date类型
+
+首先最简单的方式,就是不使用JDK8中引入的时间API,改为使用Date。
+
+这种方式没有直接解决问题,而是有些逃避问题了。
+
+
+
+### 5.1 writeReplace / readResolve 方法
+
+修改Instant的writeReplace方法可能不是很方便,我们可以为DemoDTO添加writeReplace方法。总体思路的话就是:provider把Instant拆解成支持的数据类型,到了consumer再转化成Instant。
+
+```java
+public class DemoDTO implements Serializable {
+
+ private static final long serialVersionUID = -311647434535770294L;
+
+ private String consumer;
+ private Instant instant;
+
+
+ // 序列化时JavaSerializer会调用此方法,然后序列化替代类对象repl
+ private DemoDTOHandle writeReplace() {
+ System.out.println("call writeReplace");
+ DemoDTOHandle repl = new DemoDTOHandle();
+ repl.setConsumer(consumer);
+ if (instant != null) {
+ repl.setSeconds(instant.getEpochSecond());
+ repl.setNanos(instant.getNano());
+ }
+ return repl;
+ }
+
+ // getter,setter略...
+}
+```
+
+DemoDTOHandle中Instant拆成seconds、nanos字段存储,然后会代替DemoDTO进行序列化传输。代码如下。
+
+```java
+public class DemoDTOHandle implements HessianHandle, Serializable {
+
+ private static final long serialVersionUID = -8532997896892606136L;
+
+ private String consumer;
+
+ private long seconds;
+ private int nanos;
+
+ // 反序列化时,JavaDeserializer会调用此方法,重新组装实际的类
+ private DemoDTO readResolve() {
+ System.out.println("call readResolve");
+ DemoDTO dto = new DemoDTO();
+ dto.setConsumer(consumer);
+ dto.setInstant(Instant.ofEpochSecond(seconds, nanos));
+ return dto;
+ }
+
+ // getter,setter略...
+}
+```
+
+DemoDTOHandle为什么要实现HessianHandle接口呢?答案在`SerializerFactory#getObjectDeserializer(String, Class)`中,如果不是HessianHandle实现类的话,是得不到DemoDTOHandle相关的Deserializer,只有DemoDTO相关的Deserializer,那么就无法使用DemoDTOHandle中的readResolve方法转换回对象了。
+
+![image-20220504103337575](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/image-20220504103337575.png)
+
+
+
+### 5.2 修改序列化方式
+
+之前提到,使用的是dubbo协议、hessian2序列化方式。事实上dubbo协议同样可以使用其他多种序列化方式。下面列举的序列化方式,都可以支持JDK8时间API的序列化处理。
+
+```xml
+
+
+
+
+
+
+
+
+
+```
+
+当然如果升级了Dubbo版本的话,会支持更多的序列化方式。
+
+
+
+### 5.3 升级Dubbo版本(√)
+
+当然,使用的Dubbo版本是2.5.3,实在是太老了。搜索maven仓库,看到2.5.3版本更新时间是2012年,十年前。
+
+![image-20220503122841533](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/image-20220503122841533.png)
+
+**可以选择升级到2.6.x版本**。会发现2.6.x版本在序列化方面也做了一些优化。新版本不仅包含了新功能,扩展性也有所提升。但是需要注意,如果是升级2.6.6+版本,需要关注netty版本的变化。
+
+#### 2.6.x 支持JDK8时间API
+
+在2.6.x版本中,在SerializerFactory 中增加了对JDK8时间API的处理,加入了Java8TimeSerializer以及一系列的HessianHandle。**意味着2.6.x版本Dubbo将不再有本文开头遇到的问题。**
+
+![image-20220503123444697](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/image-20220503123444697.png)
+
+#### 2.6.x 支持扩展SerializerFactory
+
+同时SerializerFactory 也提供了一定的扩展性,支持添加你想要的SerializerFactory。如果还有目标序列化方式无法处理的类型,可以自定义SerializerFactory、Serializer,只要将自定义SerializerFactory添加到当前序列化方式的SerializerFactory 中,就可以轻松扩展。**在2.5.x上是不支持这种扩展的**。
+
+```java
+// SerializerFactory.java 2.6.x
+public class SerializerFactory extends AbstractSerializerFactory {
+ // 略...
+
+ protected ArrayList _factories = new ArrayList();
+ // 略...
+
+ public void addFactory(AbstractSerializerFactory factory) {
+ _factories.add(factory);
+ }
+
+ public Serializer getSerializer(Class cl) throws HessianProtocolException{
+ Serializer serializer;
+ serializer = (Serializer) _staticSerializerMap.get(cl);
+ if (serializer != null)
+ return serializer;
+
+ if (_cachedSerializerMap != null) {
+ synchronized (_cachedSerializerMap) {
+ serializer = (Serializer) _cachedSerializerMap.get(cl);
+ }
+ if (serializer != null)
+ return serializer;
+ }
+
+ // 遍历扩展的SerializerFactory。尝试由扩展的SerializerFactory提供Serializer
+ // 2.5.3没有这个功能
+ for (int i = 0; serializer == null && _factories != null && i < _factories.size(); i++) {
+ AbstractSerializerFactory factory;
+ factory = (AbstractSerializerFactory) _factories.get(i);
+ serializer = factory.getSerializer(cl);
+ }
+
+ if (serializer != null) {
+ }
+
+ else if (JavaSerializer.getWriteReplace(cl) != null)
+ serializer = new JavaSerializer(cl, _loader);
+ else if (HessianRemoteObject.class.isAssignableFrom(cl))
+ serializer = new RemoteSerializer();
+ // else if (BurlapRemoteObject.class.isAssignableFrom(cl))
+ // serializer = new RemoteSerializer();
+ else if (Map.class.isAssignableFrom(cl)) {
+ if (_mapSerializer == null)
+ _mapSerializer = new MapSerializer();
+ serializer = _mapSerializer;
+ }
+ else if (Collection.class.isAssignableFrom(cl)) {
+ if (_collectionSerializer == null) {
+ _collectionSerializer = new CollectionSerializer();
+ }
+ serializer = _collectionSerializer;
+ }
+ else if (cl.isArray())
+ serializer = new ArraySerializer();
+ else if (Throwable.class.isAssignableFrom(cl))
+ serializer = new ThrowableSerializer(cl, getClassLoader());
+ else if (InputStream.class.isAssignableFrom(cl))
+ serializer = new InputStreamSerializer();
+ else if (Iterator.class.isAssignableFrom(cl))
+ serializer = IteratorSerializer.create();
+
+ else if (Enumeration.class.isAssignableFrom(cl))
+ serializer = EnumerationSerializer.create();
+ else if (Calendar.class.isAssignableFrom(cl))
+ serializer = CalendarSerializer.create();
+ else if (Locale.class.isAssignableFrom(cl))
+ serializer = LocaleSerializer.create();
+ else if (Enum.class.isAssignableFrom(cl))
+ serializer = new EnumSerializer(cl);
+ if (serializer == null)
+ serializer = getDefaultSerializer(cl);
+ if (_cachedSerializerMap == null)
+ _cachedSerializerMap = new HashMap(8);
+
+ synchronized (_cachedSerializerMap) {
+ _cachedSerializerMap.put(cl, serializer);
+ }
+ return serializer;
+ }
+}
+```
+
+
+
+### 5.4 SPI扩展Serialization
+
+这个方法理论上是完全可行的,但是有些过于大动干戈了。总体思路是:借助Dubbo的SPI机制,对Serialization进行扩展,基于hessian2的SerializerFactory ,对Serializer、Deserializer进行扩展。
+
+
+
+## 6 写在最后
+
+最开始计划用一两天的时间写完这篇博客,但是过程中又不断的遇到问题、解决问题,最终花了整个五一假期才完成。通过这个问题,算是对Dubbo框架的序列化这块有了一些了解了。
+
+
+
+---
+
+> 作者: [dingyufan](https://github.com/dingyufan)
+> URL: https://dingyufan.github.io/posts/dubbo-stack-overflow-due-to-jdk8-instant/
+
diff --git "a/posts/githubpages-hugo\346\220\255\345\273\272\344\270\252\344\272\272\345\215\232\345\256\242/index.html" "b/posts/githubpages-hugo\346\220\255\345\273\272\344\270\252\344\272\272\345\215\232\345\256\242/index.html"
new file mode 100644
index 00000000..ee9665ce
--- /dev/null
+++ "b/posts/githubpages-hugo\346\220\255\345\273\272\344\270\252\344\272\272\345\215\232\345\256\242/index.html"
@@ -0,0 +1,131 @@
+GitHub Pages + Hugo搭建个人博客 - dingyufan's blog 2022-04-10 2023-09-15 约 2395 字
0 前言 去年年底开始,就一直有写博客的想法,一方面来沉淀自己的技术,另一方面也希望能对他人有些小小的帮助。如今各种平台非常的多,但是不是广告铺天盖地,就是样式固定,不够个性化。最终决定借助Github Pages来搭建一个属于自己的个性化博客。
Github Pages 是由GitHub提供的一个免费构建网站的方式。通过创建名为github用户名.github.io
的仓库,上传文件后,就可以通过 http://github用户名.github.io
访问你的网站。
GIthub Pages默认使用Jekyll 构建静态网站,由于Jekyll依赖于Ruby,并且个人对Ruby完全不熟悉,所以最终选择了基于Golang的静态网站生成器Hugo 来生成静态网站,然后托管在Github上。
1 创建github仓库 创建一个 名为github用户名.github.io
的仓库,github用户名
填写自己的GIthub用户名。比如我,仓库名就是dingyufan.github.io
。
创建之后,是一个空仓库,先放着暂时不管。
2 安装Golang Windows系统安装Golang非常简单,下载msi安装包 ,直接安装即可。默认会安装到 C:\Go
目录下,同时C:\Go\bin
目录也会被添加到PATH 环境变量中。可以通过go version
验证安装的版本。目前最新版本应该是go1.18,我是之前安装的老版本。
3 安装hugo 创建文件夹C:\Hugo
、C:\Hugo\bin
、C:\Hugo\sites
。其中bin目录用来存放hugo可执行程序,sites目录则用来保存站点。
从Github下载Hugo发布的安装包 ,注意要使用extended版本 ,我下载的就是 hugo_extended_0.96.0_Windows-64bit.zip
将下载的hugo解压,并把解压得到的内容放到 C:\Hugo\bin
下。
为了方便使用hugo.exe,我们把C:\Hugo\bin
添加到环境变量PATH中。
4 创建网站 准备完全后,就可以开始构建网站了。进入C:\Hugo\sites
目录,使用hugo new site 网站名
创建网站。
注意,为了方便关联Github仓库,建议网站名与Github仓库名一致,比如我站点名、Github仓库名都为 dingyufan.github.io。
5 修改主题 Hugo拥有非常丰富的主题,可以实现各种个性化需求。我这次选择的主题是 FixIt 。
安装主题的方式有很多
注意:推荐使用第三种方式!推荐使用第三种方式!推荐使用第三种方式!
否则之后Github Pages在构建时会有异常!No url found for submodule path 'themes/FixIt' in .gitmodules
安装主题之后,如何选择使用该主题呢?修改站点下配置文件config.toml
修改其中的 theme 选项,同时可以根据FitIt主题说明 ,设置更多个性化配置。
我的config.toml文件基础配置如下:
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+
baseURL = "http://dingyufan.github.io/"
+# [en, zh-cn, fr, ...] 设置默认的语言
+defaultContentLanguage = "zh-cn"
+# 网站语言,仅在这里 CN 大写
+languageCode = "zh-CN"
+# 是否包括中日韩文字
+hasCJKLanguage = true
+# 网站标题
+title = "我的全新 Hugo 网站"
+# 文章发布位置
+publishDir = "docs"
+
+ # 更改使用 Hugo 构建网站时使用的默认主题
+theme = "FixIt"
+
+ [ params ]
+ # FixIt 主题版本
+ version = "0.2.X"
+
+ [ menu ]
+ [[ menu . main ]]
+ identifier = "posts"
+ # 你可以在名称(允许 HTML 格式)之前添加其他信息,例如图标
+ pre = ""
+ # 你可以在名称(允许 HTML 格式)之后添加其他信息,例如图标
+ post = ""
+ name = "文章"
+ url = "/posts/"
+ # 当你将鼠标悬停在此菜单链接上时,将显示的标题
+ title = ""
+ weight = 1
+ [[ menu . main ]]
+ identifier = "categories"
+ pre = ""
+ post = ""
+ name = "分类"
+ url = "/categories/"
+ title = ""
+ weight = 2
+ [[ menu . main ]]
+ identifier = "tags"
+ pre = ""
+ post = ""
+ name = "标签"
+ url = "/tags/"
+ title = ""
+ weight = 3
+
+ # Hugo 解析文档的配置
+[ markup ]
+ # 语法高亮设置 (https://gohugo.io/content-management/syntax-highlighting)
+ [ markup . highlight ]
+ # false 是必要的设置 (https://github.com/Lruihao/FixIt/issues/43)
+ noClasses = false
6 写文章 创建文章。hugo new posts/文件名.md
, 会在站点的 content/post路径下创建一个md文件。这就是要写的博文。
注意,默认创建的md文件是草稿。草稿在构建网站时是会被忽略的。所以一定要在发布前,修改文件头中 draft为false 。
在里面写一行 hello github pages
可以通过hugo serve
命令启动本地预览,通过localhost:1313即可访问预览。
看到预览效果
7 发布网站 Hugo默认会把构建后的网站放在 public 文件夹下,但是为了方用github统一管理 发布前、后的内容,我想要将构建后网站路径,修改为放在docs文件夹下。
可以通过-d docs
指定构建后放在docs文件夹下。也可以在config.toml中添加一行配置publishDir = "docs"
,这样就不需要在构建时使用 -d ,直接执行 hugo
就可以完成侯建并放入docs文件夹。
构建网站之后,需要将网站托管到Github上,即 上传到刚开始创建的空仓库里。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+
git init
+ git add -A
+ git commit -m 'init'
+ git branch -M main
+ git remote add origin git@githubcom:dingyufan/dingyufan.github.io.git
+ git push -u origin main
8 Github Pages配置 提交代码之后,需要修改Github Pages配置。指定Github Pages发布docs文件夹下的内容。
这样,仓库里的内容就是hugo生成的site的全部内容,docs文件夹下则是构建后的静态页面。这样可以对这个site完整的进行版本控制。
9 绑定域名 在完成以上步骤后,已经可以通过 http://dingyufan.github.io
访问到网站了。
Github Pages还支持自定义域名。我希望通能过自己的域名 http://blog.dingyufan.cn
访问
配置后,要在域名注册商那里修改 域名解析规则。添加CNAME记录类型。
待解析生效之后,即可通过自定义域名访问。
\ No newline at end of file
diff --git "a/posts/githubpages-hugo\346\220\255\345\273\272\344\270\252\344\272\272\345\215\232\345\256\242/index.md" "b/posts/githubpages-hugo\346\220\255\345\273\272\344\270\252\344\272\272\345\215\232\345\256\242/index.md"
new file mode 100644
index 00000000..90572676
--- /dev/null
+++ "b/posts/githubpages-hugo\346\220\255\345\273\272\344\270\252\344\272\272\345\215\232\345\256\242/index.md"
@@ -0,0 +1,246 @@
+# GitHub Pages + Hugo搭建个人博客
+
+
+
+
+## 0 前言
+
+去年年底开始,就一直有写博客的想法,一方面来沉淀自己的技术,另一方面也希望能对他人有些小小的帮助。如今各种平台非常的多,但是不是广告铺天盖地,就是样式固定,不够个性化。最终决定借助Github Pages来搭建一个属于自己的个性化博客。
+
+[Github Pages](https://pages.github.com/) 是由GitHub提供的一个免费构建网站的方式。通过创建名为`github用户名.github.io`的仓库,上传文件后,就可以通过 `http://github用户名.github.io`访问你的网站。
+
+GIthub Pages默认使用[Jekyll](https://jekyllrb.com/)构建静态网站,由于Jekyll依赖于Ruby,并且个人对Ruby完全不熟悉,所以最终选择了基于Golang的静态网站生成器[Hugo](https://gohugo.io/)来生成静态网站,然后托管在Github上。
+
+
+
+## 1 创建github仓库
+
+创建一个 名为`github用户名.github.io`的仓库,`github用户名`填写自己的GIthub用户名。比如我,仓库名就是`dingyufan.github.io`。
+
+![create-repository](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/create-repository.png)
+
+创建之后,是一个空仓库,先放着暂时不管。
+
+
+
+## 2 安装Golang
+
+Windows系统安装Golang非常简单,[下载msi安装包](https://studygolang.com/dl),直接安装即可。默认会安装到 `C:\Go` 目录下,同时`C:\Go\bin` 目录也会被添加到PATH 环境变量中。可以通过`go version`验证安装的版本。目前最新版本应该是go1.18,我是之前安装的老版本。
+
+![微信截图_20220410185748](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20220410185748.png)
+
+
+
+## 3 安装hugo
+
+创建文件夹`C:\Hugo`、`C:\Hugo\bin`、`C:\Hugo\sites`。其中bin目录用来存放hugo可执行程序,sites目录则用来保存站点。
+
+![微信截图_20220409125654](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20220409125654.png)
+
+
+
+从Github下载[Hugo发布的安装包](https://github.com/gohugoio/hugo/releases),注意要使用**extended版本**,我下载的就是 hugo_extended_0.96.0_Windows-64bit.zip
+
+![微信截图_20220409130146](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20220409130146.png)
+
+
+
+将下载的hugo解压,并把解压得到的内容放到 `C:\Hugo\bin`下。
+
+![微信截图_20220409125710](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20220409125710.png)
+
+为了方便使用hugo.exe,我们把`C:\Hugo\bin`添加到环境变量PATH中。
+
+![image-20220410191852967](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/image-20220410191852967.png)
+
+
+
+
+
+## 4 创建网站
+
+准备完全后,就可以开始构建网站了。进入`C:\Hugo\sites`目录,使用`hugo new site 网站名`创建网站。
+
+注意,为了方便关联Github仓库,建议网站名与Github仓库名一致,比如我站点名、Github仓库名都为 dingyufan.github.io。
+
+![微信截图_20220409130406](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20220409130406.png)
+
+
+
+## 5 修改主题
+
+Hugo拥有非常丰富的主题,可以实现各种个性化需求。我这次选择的主题是 [FixIt](https://github.com/Lruihao/FixIt)。
+
+安装主题的方式有很多
+
+- 一:可以直接下载主题,放置在站点的 themes文件夹下。需要注意的是,下载的主题要放在themes下和主题同名的文件夹内。比如FixIt,放置在`C:\Hugo\sites\dingyufan.github.io\themes\FixIt`中,文件夹不存在则自行创建;
+
+- 二:可以把主题克隆到 `themes` 目录。
+
+ ```bash
+ cd C:/Hugo/sites/dingyufan.github.io
+ git clone https://github.com/Lruihao/FixIt.git themes/FixIt
+ ```
+
+- **三:初始化仓库,并将主题仓库作为你的网站目录的子模块**。
+
+ ```bash
+ cd C:/Hugo/sites/dingyufan.github.io
+ git init
+ git submodule add https://github.com/Lruihao/FixIt.git themes/FixIt
+ ```
+
+
+
+注意:**推荐使用第三种方式!推荐使用第三种方式!推荐使用第三种方式!**
+
+否则之后Github Pages在构建时会有异常!`No url found for submodule path 'themes/FixIt' in .gitmodules`
+
+
+
+安装主题之后,如何选择使用该主题呢?修改站点下配置文件config.toml
+
+![image-20220410195927557](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/image-20220410195927557.png)
+
+修改其中的 theme 选项,同时可以根据[FitIt主题说明](https://fixit.lruihao.cn/zh-cn/theme-documentation-basics/),设置更多个性化配置。
+
+我的config.toml文件基础配置如下:
+
+```toml
+baseURL = "http://dingyufan.github.io/"
+# [en, zh-cn, fr, ...] 设置默认的语言
+defaultContentLanguage = "zh-cn"
+# 网站语言,仅在这里 CN 大写
+languageCode = "zh-CN"
+# 是否包括中日韩文字
+hasCJKLanguage = true
+# 网站标题
+title = "我的全新 Hugo 网站"
+# 文章发布位置
+publishDir = "docs"
+
+# 更改使用 Hugo 构建网站时使用的默认主题
+theme = "FixIt"
+
+[params]
+ # FixIt 主题版本
+ version = "0.2.X"
+
+[menu]
+ [[menu.main]]
+ identifier = "posts"
+ # 你可以在名称(允许 HTML 格式)之前添加其他信息,例如图标
+ pre = ""
+ # 你可以在名称(允许 HTML 格式)之后添加其他信息,例如图标
+ post = ""
+ name = "文章"
+ url = "/posts/"
+ # 当你将鼠标悬停在此菜单链接上时,将显示的标题
+ title = ""
+ weight = 1
+ [[menu.main]]
+ identifier = "categories"
+ pre = ""
+ post = ""
+ name = "分类"
+ url = "/categories/"
+ title = ""
+ weight = 2
+ [[menu.main]]
+ identifier = "tags"
+ pre = ""
+ post = ""
+ name = "标签"
+ url = "/tags/"
+ title = ""
+ weight = 3
+
+# Hugo 解析文档的配置
+[markup]
+ # 语法高亮设置 (https://gohugo.io/content-management/syntax-highlighting)
+ [markup.highlight]
+ # false 是必要的设置 (https://github.com/Lruihao/FixIt/issues/43)
+ noClasses = false
+```
+
+
+
+## 6 写文章
+
+创建文章。`hugo new posts/文件名.md`, 会在站点的 content/post路径下创建一个md文件。这就是要写的博文。
+
+![微信截图_20220409191339](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20220409191339.png)
+
+注意,默认创建的md文件是草稿。草稿在构建网站时是会被忽略的。所以**一定要在发布前,修改文件头中 draft为false**。
+
+在里面写一行 hello github pages
+
+![微信截图_20220409191751](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20220409191751.png)
+
+
+
+可以通过`hugo serve`命令启动本地预览,通过localhost:1313即可访问预览。
+
+![微信截图_20220409191826](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20220409191826.png)
+
+
+
+看到预览效果
+
+![微信截图_20220409191900](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20220409191900.png)
+
+
+
+## 7 发布网站
+
+Hugo默认会把构建后的网站放在 public 文件夹下,但是为了方用github统一管理 发布前、后的内容,我想要将构建后网站路径,修改为放在docs文件夹下。
+
+可以通过`-d docs`指定构建后放在docs文件夹下。**也可以在config.toml中添加一行配置`publishDir = "docs"`,这样就不需要在构建时使用 -d ,直接执行 `hugo`就可以完成侯建并放入docs文件夹。**
+
+![微信截图_20220409191950](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20220409191950.png)
+
+
+
+构建网站之后,需要将网站托管到Github上,即 上传到刚开始创建的空仓库里。
+
+```bash
+git init
+git add -A
+git commit -m 'init'
+git branch -M main
+git remote add origin git@githubcom:dingyufan/dingyufan.github.io.git
+git push -u origin main
+```
+
+
+
+## 8 Github Pages配置
+
+提交代码之后,需要修改Github Pages配置。指定Github Pages发布docs文件夹下的内容。
+
+![image-20220410202202488](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/image-20220410202202488.png)
+
+这样,仓库里的内容就是hugo生成的site的全部内容,docs文件夹下则是构建后的静态页面。这样可以对这个site完整的进行版本控制。
+
+
+
+## 9 绑定域名
+
+在完成以上步骤后,已经可以通过 `http://dingyufan.github.io`访问到网站了。
+
+Github Pages还支持自定义域名。我希望通能过自己的域名 `http://blog.dingyufan.cn` 访问
+
+![image-20220410202804130](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/image-20220410202804130.png)
+
+配置后,要在域名注册商那里修改 域名解析规则。添加CNAME记录类型。
+
+![image-20220410203010269](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/image-20220410203010269.png)
+
+待解析生效之后,即可通过自定义域名访问。
+
+
+---
+
+> 作者: [dingyufan](https://github.com/dingyufan)
+> URL: https://dingyufan.github.io/posts/githubpages-hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/
+
diff --git a/posts/index.html b/posts/index.html
new file mode 100644
index 00000000..43804312
--- /dev/null
+++ b/posts/index.html
@@ -0,0 +1,7 @@
+所有文章 - dingyufan's blog
\ No newline at end of file
diff --git a/posts/index.xml b/posts/index.xml
new file mode 100644
index 00000000..45336295
--- /dev/null
+++ b/posts/index.xml
@@ -0,0 +1 @@
+所有文章 - dingyufan's blog https://dingyufan.github.io/posts/所有文章 | dingyufan's blog Hugo -- gohugo.io zh-CN dingyufan@outlook.com (dingyufan) dingyufan@outlook.com (dingyufan) Sat, 09 Sep 2023 00:00:00 +0000 浅尝树莓派4B(Ubuntu22.04)安装Clash作为代理服务器 https://dingyufan.github.io/posts/%E6%B5%85%E5%B0%9D%E6%A0%91%E8%8E%93%E6%B4%BE4bubuntu22.04%E5%AE%89%E8%A3%85clash%E4%BD%9C%E4%B8%BA%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/Sat, 09 Sep 2023 00:00:00 +0000 dingyufan https://dingyufan.github.io/posts/%E6%B5%85%E5%B0%9D%E6%A0%91%E8%8E%93%E6%B4%BE4bubuntu22.04%E5%AE%89%E8%A3%85clash%E4%BD%9C%E4%B8%BA%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/ JDK8日期时间API导致Dubbo调用StackOverflow错误 https://dingyufan.github.io/posts/dubbo-stack-overflow-due-to-jdk8-instant/Sat, 30 Apr 2022 11:34:02 +0800 dingyufan https://dingyufan.github.io/posts/dubbo-stack-overflow-due-to-jdk8-instant/ GitHub Pages + Hugo搭建个人博客 https://dingyufan.github.io/posts/githubpages-hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/Sun, 10 Apr 2022 11:34:02 +0800 dingyufan https://dingyufan.github.io/posts/githubpages-hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/
\ No newline at end of file
diff --git a/posts/page/1/index.html b/posts/page/1/index.html
new file mode 100644
index 00000000..c0c989c8
--- /dev/null
+++ b/posts/page/1/index.html
@@ -0,0 +1 @@
+https://dingyufan.github.io/posts/
\ No newline at end of file
diff --git "a/posts/\346\265\205\345\260\235\346\240\221\350\216\223\346\264\2764bubuntu22.04\345\256\211\350\243\205clash\344\275\234\344\270\272\344\273\243\347\220\206\346\234\215\345\212\241\345\231\250/index.html" "b/posts/\346\265\205\345\260\235\346\240\221\350\216\223\346\264\2764bubuntu22.04\345\256\211\350\243\205clash\344\275\234\344\270\272\344\273\243\347\220\206\346\234\215\345\212\241\345\231\250/index.html"
new file mode 100644
index 00000000..195932b2
--- /dev/null
+++ "b/posts/\346\265\205\345\260\235\346\240\221\350\216\223\346\264\2764bubuntu22.04\345\256\211\350\243\205clash\344\275\234\344\270\272\344\273\243\347\220\206\346\234\215\345\212\241\345\231\250/index.html"
@@ -0,0 +1,136 @@
+浅尝树莓派4B(Ubuntu22.04)安装Clash作为代理服务器 - dingyufan's blog 2023-09-09 2023-09-15 约 3019 字
0 前言 树莓派(Raspberry Pi )是一种基于ARM架构的单板计算机,仅有银行卡大小,本意是提供一种低成本的计算机学习硬件。但前几年由于各种原因,价格一路高歌猛进,让人望而却步。最近逛淘宝发现,树莓派价格差不多回归了正常架构,于是果断下手买一个尝尝。
1 安装 Ubuntu22.04 到手的是 树莓派4B 4G内存版,因为内存足够,准备安装带GUI界面的 Ubuntu Desktop 22.04 。
1.1 烧录操作系统 树莓派的存储是在一张SD卡上,所以需要通过 Raspberry Pi Imager 将操作系统烧录到SD卡中。
不需要提前下载镜像,直接可以在工具中选择系统,然后选中目标SD卡,点击烧录。
1
+ 2
+ 3
+
> Other general-purpose OS
+ > Ubuntu
+ > Ubuntu Desktop 22.04.3 LTS (64-bit)
烧录完成后,将SD卡插入树莓派,通电启动即可。系统安装后,即可通过 micro HMDI 连接到显示设备,并使用键盘、鼠标进行操作。首次启动会引导设置语言时区、用户名主机名等信息,不在此赘述。
1.2 Ubuntu软件源修改 树莓派切换镜像源的方式和一般Ubuntu设备并无不同,唯一需要注意的是 要使用 ubuntu-ports 镜像 ,这里面才有arm64的源。
我们使用清华镜像站 ,页面比较友好,可以直接在页面切换选项并产生配置内容。
备份原 sources.list
,将清华源信息覆写到 sources.list
,执行 sudo apt update
更新软件包信息。
1.3 固定网络IP 为了方便ssh登录,以及作为网络代理服务器,需要设置一个固定的IP地址。
编辑netplan配置文件 /etc/netplan/01-network-manager-all.yaml
。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+
network :
+ version : 2
+ renderer : NetworkManager
+ # 无线网络配置
+ wifis :
+ # 无线网卡 wlan0
+ wlan0 :
+ # 关闭dhcp
+ dhcp4 : false
+ # 固定IP, /24 用来表示掩码信息
+ addresses : [ 192.168.31.250 /24]
+ optional : true
+ # 配置项 gateway4 在新版本中已弃用,通过routes配置网关
+ routes :
+ - to : default
+ via : 192.168.31.1
+ # DNS服务
+ nameservers :
+ addresses : [ 114.114.114.114 ]
+ # 配置WiFi信息
+ access-points :
+ "WiFi名称" :
+ password : 'WiFi密码'
配置修改完成后,使用 sudo netplan try
测试网络配置,无异常即可使用 sudo netplan apply
应用配置。
1.4 安装 openssh-server 配置完成后,发现无法ssh访问,发现默认没有安装 openssh-server,通过apt安装。安装后即可远程ssh访问。
1
+
sudo apt install openssh-server
2 Clash Clash 是一个基于规则的代理工具,主要的用途不多说了,如果你知道这个软件,就说明你大概率有这个需求。这个安装方式也不限于树莓派设备,只要是运行Linux系统的设备,都可以
这次之所以选择带GUI的Ubuntu,很大一部分原因就是想要通过图形化界面,比较方便的配置Clash。
2.1 Clash for Windows Clash for Windows 是比较好用的一个 Clash GUI 客户端。
但是!它这个名字太具有迷惑性了!
它其实 支持 Windows系统!也支持 Linux!还支持 macOS !
从GitHub release页面下载,Clash.for.Windows-0.20.34-arm64-linux.tar.gz
,从它提供的各种安装包也能看出支持多平台
下载后解压,运行 cfw
,即可出现GUI界面,然后开始进行配置。
在左侧 General 菜单 中,打开 Allow LAN开关,允许局域网连接 。这样不止为本机提供网络代理,也能为局域网其他设备提供代理。 在左侧 Profiles 菜单 中,将机场提供的订阅地址填入输入框,下载得到代理规则 。config.yaml为默认配置,sub为订阅到的规则。 到这为止,Clash for Windows就已经准备完毕了,随时可以使用了。
2.2 Clash(无图形界面) 如是是无图形界面的情况,可以通过命令安装Clash。
下载Clash,注意版本,这里以树莓派使用的ARM版本为例
1
+ 2
+
mkdir ~/clash && cd ~/clash
+ wget https://github.com/Dreamacro/clash/releases/download/v1.18.0/clash-linux-armv7-v1.18.0.gz
解压后重命名
1
+ 2
+
gunzip clash-linux-armv7-v1.18.0.gz
+ mv clash-linux-armv7-v1.18.0 clash
移动clash到指定位置,并赋予执行权限
1
+ 2
+
sudo cp clash /usr/local/bin
+ sudo chmod +x /usr/local/bin/clash
初始化执行,自动生成config.yaml 和 Country.mmdb
1
+ 2
+ 3
+ 4
+ 5
+
sudo /usr/local/bin/clash -d /etc/clash/
+ # 输出
+# INFO[0000] Can't find config, create a initial config file
+# INFO[0000] Can't find MMDB, start download
+# INFO[0003] inbound mixed://127.0.0.1:7890 create success.
ctrl+c中断执行,修改config.yaml 。这个就替换成你机场Clash订阅地址返回的yaml代理站点内容。
需要注意的是,在config.yaml中有几个配置,可能需要自己修改一下,因为机场提供的值可能不是你想要的
关于端口
代理端口分为 http代理端口(port)
、socks代理端口(socks-port)
和 混合代理端口 (mixed-port)
。这里建议直接将混合代理端口 (mixed-port)
设置为7890,其他两项设为0。这样不管是http、socks都是用同一个7890端口,更加方便
关于外部控制
Clash是支持通过 RESTful 接口来对Clash进行功能配置的,目前市面上的GUI客户端配置管理也是基于这个方式。
config.yaml中有一项配置 external-controller
,修改为 0.0.0.0:9090
这样方便我从局域网其他设备对其进行控制。
还有一项配置 secret
主要用于 RESTful 控制接口的身份认证密码。如果是局域网不对外的话,可暂时不配。
clash.razord.top
对于没有GUI的Clash,可以在其他设备上通过这个网站,获得一个控制的Dashboard。
网站打开时会让填写 Host、端口、秘钥,填写后就可以借助这个网页,对无图形界面设备上安装的Clash进行配置。具体原理的话就是页面发起配置对应功能的http请求。
代理配置完后,创建systemd 配置文件 /etc/systemd/system/clash.service
,让我们的Clash能够开机自启。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+
[Unit]
+Description = Clash 守护进程, Go 语言实现的基于规则的代理.
+After = network-online.target
+
+ [Service]
+Type = simple
+Restart = always
+ExecStart = /usr/local/bin/clash -d /etc/clash
+
+ [Install]
+WantedBy = multi-user.target
重新加载 systemd,设置开机启动 和 立即启动
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+
sudo systemctl daemon-reload
+ # 设置系统启动时启动clash
+sudo systemctl enable clash
+ # 立即启动clash
+sudo systemctl start clash
+ # 查看clash运行状态
+sudo systemctl status clash
到这为止,Clash就已经准备完毕了,随时可以使用了。
2.3 使用方式 如果你使用的设备安装了SSR、V2ray或者Clash,那你可以直接在软件界面上操作,通过界面提供的功能来开启系统代理。
这里主要是介绍一下没有安装代理软件的设备,如何借助局域网内其他已安装的代理设备(即代理服务器),实现网络代理功能。
2.3.1 Windows 10 设备 在 Window 10 设置 > 网络和Internet > 代理 中,打开【使用代理服务器】开关,地址填树莓派IP地址,端口为Clash使用的7890端口。
也可以设置对 本地地址(localhost)、常见的局域网网段(192.168.x.x) 不使用代理服务器。
2.3.2 手机、iPad等无线设备 进入 无线局域网设置(WLAN设置),点击已连接的无线网络,将网络详情页面拉到底,有HTTP代理设置,填入树莓派IP地址,Clash的7890端口保存即可。
2.3.3 Ubuntu/Linux 如果是带有GUI界面的操作系统,直接在界面上配置,以Ubuntu为例,在 Settings > Network > Network Proxy 中,将代理配置中填入树莓派IP地址和Clash的7890端口保存即可
如果是没有界面的Linux系统,可以通过设置环境变量的方式设置
1
+ 2
+ 3
+
export https_proxy = http://192.168.31.250:7890
+export http_proxy = http://192.168.31.250:7890
+export all_proxy = socks5://192.168.31.250:7890
2.3.4 按需配置 总的来说,我个人并不喜欢为系统配置全局网络代理,我更喜欢给有需要的程序配置网络代理 。举个几个例子:
容器运行时containerd
使用containerd时,有些镜像国内无法拉取,而且国内没有厂商同步这些镜像,这就可以为containerd配置代理服务器,来帮助拉取这些镜像。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+
sudo mkdir -p /etc/systemd/system/containerd.service.d/
+
+ cat << EOF | sudo tee -a /etc/systemd/system/containerd.service.d/http-proxy.conf
+ [Service]
+ Environment="HTTP_PROXY=192.168.31.250:7890"
+ Environment="HTTPS_PROXY=192.168.31.250:7890"
+ EOF
+
+ sudo systemctl restart containerd
proxychains4
这是一个很简单、好用的代理工工具。你只需要通过apt安装
1
+
sudo apt install proxychains4
安装后修改配置文件 /etc/proxychains.config
,将文件最后的代理地址改为代理服务器的地址,比如 socks5 192.168.31.250 7890
即可。
使用方法的话,只需要在原本命令前添加 proxychains4 即可
1
+ 2
+ 3
+
proxychains4 wget xxx
+ proxychains4 curl xxx
+ proxychains4 git clone xxx
\ No newline at end of file
diff --git "a/posts/\346\265\205\345\260\235\346\240\221\350\216\223\346\264\2764bubuntu22.04\345\256\211\350\243\205clash\344\275\234\344\270\272\344\273\243\347\220\206\346\234\215\345\212\241\345\231\250/index.md" "b/posts/\346\265\205\345\260\235\346\240\221\350\216\223\346\264\2764bubuntu22.04\345\256\211\350\243\205clash\344\275\234\344\270\272\344\273\243\347\220\206\346\234\215\345\212\241\345\231\250/index.md"
new file mode 100644
index 00000000..3e409516
--- /dev/null
+++ "b/posts/\346\265\205\345\260\235\346\240\221\350\216\223\346\264\2764bubuntu22.04\345\256\211\350\243\205clash\344\275\234\344\270\272\344\273\243\347\220\206\346\234\215\345\212\241\345\231\250/index.md"
@@ -0,0 +1,316 @@
+# 浅尝树莓派4B(Ubuntu22.04)安装Clash作为代理服务器
+
+
+
+
+
+
+## 0 前言
+
+树莓派([Raspberry Pi](https://www.raspberrypi.com/))是一种基于ARM架构的单板计算机,仅有银行卡大小,本意是提供一种低成本的计算机学习硬件。但前几年由于各种原因,价格一路高歌猛进,让人望而却步。最近逛淘宝发现,树莓派价格差不多回归了正常架构,于是果断下手买一个尝尝。
+
+
+
+
+
+## 1 安装 Ubuntu22.04
+
+到手的是 树莓派4B 4G内存版,因为内存足够,准备安装带GUI界面的 Ubuntu Desktop 22.04 。
+
+
+
+### 1.1 烧录操作系统
+
+树莓派的存储是在一张SD卡上,所以需要通过 [Raspberry Pi Imager](https://www.raspberrypi.com/software/) 将操作系统烧录到SD卡中。
+
+不需要提前下载镜像,直接可以在工具中选择系统,然后选中目标SD卡,点击烧录。
+
+```txt
+> Other general-purpose OS
+ > Ubuntu
+ > Ubuntu Desktop 22.04.3 LTS (64-bit)
+```
+
+
+
+
+
+烧录完成后,将SD卡插入树莓派,通电启动即可。系统安装后,即可通过 micro HMDI 连接到显示设备,并使用键盘、鼠标进行操作。首次启动会引导设置语言时区、用户名主机名等信息,不在此赘述。
+
+
+
+### 1.2 Ubuntu软件源修改
+
+树莓派切换镜像源的方式和一般Ubuntu设备并无不同,唯一需要注意的是 **要使用 ubuntu-ports 镜像**,这里面才有arm64的源。
+
+我们使用[清华镜像站](https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu-ports/),页面比较友好,可以直接在页面切换选项并产生配置内容。
+
+
+
+备份原 `sources.list`,将清华源信息覆写到 `sources.list`,执行 `sudo apt update ` 更新软件包信息。
+
+
+
+### 1.3 固定网络IP
+
+为了方便ssh登录,以及作为网络代理服务器,需要设置一个固定的IP地址。
+
+编辑netplan配置文件 `/etc/netplan/01-network-manager-all.yaml`。
+
+```yaml
+network:
+ version: 2
+ renderer: NetworkManager
+ # 无线网络配置
+ wifis:
+ # 无线网卡 wlan0
+ wlan0:
+ # 关闭dhcp
+ dhcp4: false
+ # 固定IP, /24 用来表示掩码信息
+ addresses: [192.168.31.250/24]
+ optional: true
+ # 配置项 gateway4 在新版本中已弃用,通过routes配置网关
+ routes:
+ - to: default
+ via: 192.168.31.1
+ # DNS服务
+ nameservers:
+ addresses: [114.114.114.114]
+ # 配置WiFi信息
+ access-points:
+ "WiFi名称":
+ password: 'WiFi密码'
+```
+
+配置修改完成后,使用 `sudo netplan try` 测试网络配置,无异常即可使用 `sudo netplan apply` 应用配置。
+
+
+
+### 1.4 安装 openssh-server
+
+配置完成后,发现无法ssh访问,发现默认没有安装 openssh-server,通过apt安装。安装后即可远程ssh访问。
+
+```shell
+sudo apt install openssh-server
+```
+
+
+
+## 2 Clash
+
+[Clash](https://github.com/Dreamacro/clash) 是一个基于规则的代理工具,主要的用途不多说了,如果你知道这个软件,就说明你大概率有这个需求。这个安装方式也不限于树莓派设备,只要是运行Linux系统的设备,都可以
+
+
+
+这次之所以选择带GUI的Ubuntu,很大一部分原因就是想要通过图形化界面,比较方便的配置Clash。
+
+
+
+### 2.1 Clash for Windows
+
+[Clash for Windows](https://github.com/Fndroid/clash_for_windows_pkg/releases) 是比较好用的一个 Clash GUI 客户端。
+
+***但是!它这个名字太具有迷惑性了! ***
+
+***它其实 支持 Windows系统!也支持 Linux!还支持 macOS ! ***
+
+从GitHub release页面下载,`Clash.for.Windows-0.20.34-arm64-linux.tar.gz`,从它提供的各种安装包也能看出支持多平台
+
+
+
+下载后解压,运行 `cfw`,即可出现GUI界面,然后开始进行配置。
+
+
+
+1. 在左侧 **General 菜单**中,**打开 Allow LAN开关,允许局域网连接**。这样不止为本机提供网络代理,也能为局域网其他设备提供代理。
+2. 在左侧 **Profiles 菜单**中,**将机场提供的订阅地址填入输入框,下载得到代理规则**。config.yaml为默认配置,sub为订阅到的规则。
+
+
+
+到这为止,Clash for Windows就已经准备完毕了,随时可以使用了。
+
+
+
+### 2.2 Clash(无图形界面)
+
+如是是无图形界面的情况,可以通过命令安装Clash。
+
+下载Clash,注意版本,这里以树莓派使用的ARM版本为例
+
+```shell
+mkdir ~/clash && cd ~/clash
+wget https://github.com/Dreamacro/clash/releases/download/v1.18.0/clash-linux-armv7-v1.18.0.gz
+```
+
+解压后重命名
+
+```shell
+gunzip clash-linux-armv7-v1.18.0.gz
+mv clash-linux-armv7-v1.18.0 clash
+```
+
+移动clash到指定位置,并赋予执行权限
+
+```shell
+sudo cp clash /usr/local/bin
+sudo chmod +x /usr/local/bin/clash
+```
+
+初始化执行,自动生成config.yaml 和 Country.mmdb
+
+```shell
+sudo /usr/local/bin/clash -d /etc/clash/
+# 输出
+# INFO[0000] Can't find config, create a initial config file
+# INFO[0000] Can't find MMDB, start download
+# INFO[0003] inbound mixed://127.0.0.1:7890 create success.
+```
+
+ctrl+c中断执行,**修改config.yaml**。这个就替换成你机场Clash订阅地址返回的yaml代理站点内容。
+
+**需要注意的是,在config.yaml中有几个配置,可能需要自己修改一下,因为机场提供的值可能不是你想要的 **
+
+1. **关于端口**
+
+ 代理端口分为 `http代理端口(port)`、`socks代理端口(socks-port)` 和 `混合代理端口 (mixed-port)`。这里建议直接将`混合代理端口 (mixed-port)`设置为7890,其他两项设为0。这样不管是http、socks都是用同一个7890端口,更加方便
+
+2. **关于外部控制**
+
+ Clash是支持通过 RESTful 接口来对Clash进行功能配置的,目前市面上的GUI客户端配置管理也是基于这个方式。
+
+ config.yaml中有一项配置 `external-controller`,修改为 `0.0.0.0:9090` 这样方便我从局域网其他设备对其进行控制。
+
+ 还有一项配置 `secret` 主要用于 RESTful 控制接口的身份认证密码。如果是局域网不对外的话,可暂时不配。
+
+3. **clash.razord.top**
+
+ 对于没有GUI的Clash,可以在其他设备上通过这个网站,获得一个控制的Dashboard。
+
+ 网站打开时会让填写 Host、端口、秘钥,填写后就可以借助这个网页,对无图形界面设备上安装的Clash进行配置。具体原理的话就是页面发起配置对应功能的http请求。
+
+
+
+代理配置完后,创建systemd 配置文件 `/etc/systemd/system/clash.service`,让我们的Clash能够开机自启。
+
+```ini
+[Unit]
+Description=Clash 守护进程, Go 语言实现的基于规则的代理.
+After=network-online.target
+
+[Service]
+Type=simple
+Restart=always
+ExecStart=/usr/local/bin/clash -d /etc/clash
+
+[Install]
+WantedBy=multi-user.target
+```
+
+重新加载 systemd,设置开机启动 和 立即启动
+
+```shell
+sudo systemctl daemon-reload
+# 设置系统启动时启动clash
+sudo systemctl enable clash
+# 立即启动clash
+sudo systemctl start clash
+# 查看clash运行状态
+sudo systemctl status clash
+```
+
+到这为止,Clash就已经准备完毕了,随时可以使用了。
+
+
+
+### 2.3 使用方式
+
+> 如果你使用的设备安装了SSR、V2ray或者Clash,那你可以直接在软件界面上操作,通过界面提供的功能来开启系统代理。
+
+这里主要是介绍一下没有安装代理软件的设备,如何借助局域网内其他已安装的代理设备(即代理服务器),实现网络代理功能。
+
+
+
+#### 2.3.1 Windows 10 设备
+
+在 Window 10 设置 > 网络和Internet > 代理 中,打开【使用代理服务器】开关,地址填树莓派IP地址,端口为Clash使用的7890端口。
+
+也可以设置对 本地地址(localhost)、常见的局域网网段(192.168.x.x) 不使用代理服务器。
+
+
+
+
+
+#### 2.3.2 手机、iPad等无线设备
+
+进入 无线局域网设置(WLAN设置),点击已连接的无线网络,将网络详情页面拉到底,有HTTP代理设置,填入树莓派IP地址,Clash的7890端口保存即可。
+
+
+
+
+
+#### 2.3.3 Ubuntu/Linux
+
+如果是带有GUI界面的操作系统,直接在界面上配置,以Ubuntu为例,在 Settings > Network > Network Proxy 中,将代理配置中填入树莓派IP地址和Clash的7890端口保存即可
+
+
+
+如果是没有界面的Linux系统,可以通过设置环境变量的方式设置
+
+```shell
+export https_proxy=http://192.168.31.250:7890
+export http_proxy=http://192.168.31.250:7890
+export all_proxy=socks5://192.168.31.250:7890
+```
+
+
+
+#### 2.3.4 按需配置
+
+总的来说,我个人并不喜欢为系统配置全局网络代理,我更喜欢**给有需要的程序配置网络代理**。举个几个例子:
+
+
+
+**容器运行时containerd**
+
+使用containerd时,有些镜像国内无法拉取,而且国内没有厂商同步这些镜像,这就可以为containerd配置代理服务器,来帮助拉取这些镜像。
+
+```shell
+sudo mkdir -p /etc/systemd/system/containerd.service.d/
+
+cat << EOF | sudo tee -a /etc/systemd/system/containerd.service.d/http-proxy.conf
+[Service]
+Environment="HTTP_PROXY=192.168.31.250:7890"
+Environment="HTTPS_PROXY=192.168.31.250:7890"
+EOF
+
+sudo systemctl restart containerd
+```
+
+
+
+**proxychains4**
+
+这是一个很简单、好用的代理工工具。你只需要通过apt安装
+
+```shell
+sudo apt install proxychains4
+```
+
+安装后修改配置文件 `/etc/proxychains.config`,将文件最后的代理地址改为代理服务器的地址,比如 `socks5 192.168.31.250 7890`即可。
+
+使用方法的话,只需要在原本命令前添加 proxychains4 即可
+
+```shell
+proxychains4 wget xxx
+proxychains4 curl xxx
+proxychains4 git clone xxx
+```
+
+
+
+---
+
+> 作者: [dingyufan](https://github.com/dingyufan)
+> URL: https://dingyufan.github.io/posts/%E6%B5%85%E5%B0%9D%E6%A0%91%E8%8E%93%E6%B4%BE4bubuntu22.04%E5%AE%89%E8%A3%85clash%E4%BD%9C%E4%B8%BA%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/
+
diff --git a/robots.txt b/robots.txt
new file mode 100644
index 00000000..ad6dc5aa
--- /dev/null
+++ b/robots.txt
@@ -0,0 +1,68 @@
+User-agent: *
+
+Disallow: /images/
+Disallow: /js/
+Disallow: /css/
+
+Disallow: /*offline/
+Disallow: /*404.html$
+Disallow: /*.md$
+
+User-agent: MJ12bot
+Disallow: /
+
+User-agent: AhrefsBot
+Disallow: /
+
+User-agent: BLEXBot
+Disallow: /
+
+# Block SISTRIX
+User-agent: SISTRIX Crawler
+Disallow: /
+User-agent: sistrix
+Disallow: /
+User-agent: 007ac9
+Disallow: /
+User-agent: 007ac9 Crawler
+Disallow: /
+
+# Block Uptime robot
+User-agent: UptimeRobot/2.0
+Disallow: /
+
+# Block Ezooms Robot
+User-agent: Ezooms Robot
+Disallow: /
+
+# Block Perl LWP
+User-agent: Perl LWP
+Disallow: /
+
+# Block netEstate NE Crawler (+http://www.website-datenbank.de/)
+User-agent: netEstate NE Crawler (+http://www.website-datenbank.de/)
+Disallow: /
+
+# Block WiseGuys Robot
+User-agent: WiseGuys Robot
+Disallow: /
+
+# Block Turnitin Robot
+User-agent: Turnitin Robot
+Disallow: /
+
+# Block Heritrix
+User-agent: Heritrix
+Disallow: /
+
+# Block pricepi
+User-agent: pimonster
+Disallow: /
+
+User-agent: SurdotlyBot
+Disallow: /
+
+User-agent: ZoominfoBot
+Disallow: /
+
+Sitemap: https://dingyufan.github.io/sitemap.xml
diff --git a/sitemap.xml b/sitemap.xml
new file mode 100644
index 00000000..a87d4a50
--- /dev/null
+++ b/sitemap.xml
@@ -0,0 +1 @@
+https://dingyufan.github.io/categories/ 2023-09-15T01:08:37+00:00 weekly 1 https://dingyufan.github.io/tags/clash/ 2023-09-15T01:08:37+00:00 weekly 1 https://dingyufan.github.io/ 2023-09-15T01:08:37+00:00 weekly 1 https://dingyufan.github.io/posts/ 2023-09-15T01:08:37+00:00 weekly 1 https://dingyufan.github.io/tags/raspberry-pi/ 2023-09-15T01:08:37+00:00 weekly 1 https://dingyufan.github.io/tags/ 2023-09-15T01:08:37+00:00 weekly 1 https://dingyufan.github.io/categories/tech/ 2023-09-15T01:08:37+00:00 weekly 1 https://dingyufan.github.io/tags/ubuntu/ 2023-09-15T01:08:37+00:00 weekly 1 https://dingyufan.github.io/posts/%E6%B5%85%E5%B0%9D%E6%A0%91%E8%8E%93%E6%B4%BE4bubuntu22.04%E5%AE%89%E8%A3%85clash%E4%BD%9C%E4%B8%BA%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/ 2023-09-15T01:08:37+00:00 weekly 1 https://dingyufan.github.io/categories/coding/ 2023-07-28T16:32:25+08:00 weekly 0.5 https://dingyufan.github.io/tags/dubbo/ 2023-07-28T16:32:25+08:00 weekly 0.5 https://dingyufan.github.io/tags/java/ 2023-07-28T16:32:25+08:00 weekly 0.5 https://dingyufan.github.io/posts/dubbo-stack-overflow-due-to-jdk8-instant/ 2023-07-28T16:32:25+08:00 weekly 0.5 https://dingyufan.github.io/tags/github-pages/ 2023-09-15T01:08:37+00:00 weekly 1 https://dingyufan.github.io/posts/githubpages-hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/ 2023-09-15T01:08:37+00:00 weekly 1 https://dingyufan.github.io/tags/hugo/ 2023-09-15T01:08:37+00:00 weekly 1
\ No newline at end of file
diff --git a/svg/loading.min.svg b/svg/loading.min.svg
new file mode 100644
index 00000000..efb11940
--- /dev/null
+++ b/svg/loading.min.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/tags/clash/index.html b/tags/clash/index.html
new file mode 100644
index 00000000..d77a1527
--- /dev/null
+++ b/tags/clash/index.html
@@ -0,0 +1,5 @@
+Clash - 标签 - dingyufan's blog
\ No newline at end of file
diff --git a/tags/clash/index.xml b/tags/clash/index.xml
new file mode 100644
index 00000000..975c4379
--- /dev/null
+++ b/tags/clash/index.xml
@@ -0,0 +1 @@
+Clash - 标签 - dingyufan's blog https://dingyufan.github.io/tags/clash/Clash - 标签 - dingyufan's blog Hugo -- gohugo.io zh-CN dingyufan@outlook.com (dingyufan) dingyufan@outlook.com (dingyufan) Sat, 09 Sep 2023 00:00:00 +0000 浅尝树莓派4B(Ubuntu22.04)安装Clash作为代理服务器 https://dingyufan.github.io/posts/%E6%B5%85%E5%B0%9D%E6%A0%91%E8%8E%93%E6%B4%BE4bubuntu22.04%E5%AE%89%E8%A3%85clash%E4%BD%9C%E4%B8%BA%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/Sat, 09 Sep 2023 00:00:00 +0000 dingyufan https://dingyufan.github.io/posts/%E6%B5%85%E5%B0%9D%E6%A0%91%E8%8E%93%E6%B4%BE4bubuntu22.04%E5%AE%89%E8%A3%85clash%E4%BD%9C%E4%B8%BA%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/
\ No newline at end of file
diff --git a/tags/clash/page/1/index.html b/tags/clash/page/1/index.html
new file mode 100644
index 00000000..d04c4775
--- /dev/null
+++ b/tags/clash/page/1/index.html
@@ -0,0 +1 @@
+https://dingyufan.github.io/tags/clash/
\ No newline at end of file
diff --git a/tags/dubbo/index.html b/tags/dubbo/index.html
new file mode 100644
index 00000000..045cec72
--- /dev/null
+++ b/tags/dubbo/index.html
@@ -0,0 +1,5 @@
+Dubbo - 标签 - dingyufan's blog
\ No newline at end of file
diff --git a/tags/dubbo/index.xml b/tags/dubbo/index.xml
new file mode 100644
index 00000000..45fc6fd6
--- /dev/null
+++ b/tags/dubbo/index.xml
@@ -0,0 +1 @@
+Dubbo - 标签 - dingyufan's blog https://dingyufan.github.io/tags/dubbo/Dubbo - 标签 - dingyufan's blog Hugo -- gohugo.io zh-CN dingyufan@outlook.com (dingyufan) dingyufan@outlook.com (dingyufan) Sat, 30 Apr 2022 11:34:02 +0800 JDK8日期时间API导致Dubbo调用StackOverflow错误 https://dingyufan.github.io/posts/dubbo-stack-overflow-due-to-jdk8-instant/Sat, 30 Apr 2022 11:34:02 +0800 dingyufan https://dingyufan.github.io/posts/dubbo-stack-overflow-due-to-jdk8-instant/
\ No newline at end of file
diff --git a/tags/dubbo/page/1/index.html b/tags/dubbo/page/1/index.html
new file mode 100644
index 00000000..3ae0ef74
--- /dev/null
+++ b/tags/dubbo/page/1/index.html
@@ -0,0 +1 @@
+https://dingyufan.github.io/tags/dubbo/
\ No newline at end of file
diff --git a/tags/github-pages/index.html b/tags/github-pages/index.html
new file mode 100644
index 00000000..f48b4ae4
--- /dev/null
+++ b/tags/github-pages/index.html
@@ -0,0 +1,5 @@
+Github Pages - 标签 - dingyufan's blog
\ No newline at end of file
diff --git a/tags/github-pages/index.xml b/tags/github-pages/index.xml
new file mode 100644
index 00000000..645c3669
--- /dev/null
+++ b/tags/github-pages/index.xml
@@ -0,0 +1 @@
+Github Pages - 标签 - dingyufan's blog https://dingyufan.github.io/tags/github-pages/Github Pages - 标签 - dingyufan's blog Hugo -- gohugo.io zh-CN dingyufan@outlook.com (dingyufan) dingyufan@outlook.com (dingyufan) Sun, 10 Apr 2022 11:34:02 +0800 GitHub Pages + Hugo搭建个人博客 https://dingyufan.github.io/posts/githubpages-hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/Sun, 10 Apr 2022 11:34:02 +0800 dingyufan https://dingyufan.github.io/posts/githubpages-hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/
\ No newline at end of file
diff --git a/tags/github-pages/page/1/index.html b/tags/github-pages/page/1/index.html
new file mode 100644
index 00000000..07b8d561
--- /dev/null
+++ b/tags/github-pages/page/1/index.html
@@ -0,0 +1 @@
+https://dingyufan.github.io/tags/github-pages/
\ No newline at end of file
diff --git a/tags/hugo/index.html b/tags/hugo/index.html
new file mode 100644
index 00000000..fd229e8e
--- /dev/null
+++ b/tags/hugo/index.html
@@ -0,0 +1,5 @@
+Hugo - 标签 - dingyufan's blog
\ No newline at end of file
diff --git a/tags/hugo/index.xml b/tags/hugo/index.xml
new file mode 100644
index 00000000..3055117c
--- /dev/null
+++ b/tags/hugo/index.xml
@@ -0,0 +1 @@
+Hugo - 标签 - dingyufan's blog https://dingyufan.github.io/tags/hugo/Hugo - 标签 - dingyufan's blog Hugo -- gohugo.io zh-CN dingyufan@outlook.com (dingyufan) dingyufan@outlook.com (dingyufan) Sun, 10 Apr 2022 11:34:02 +0800 GitHub Pages + Hugo搭建个人博客 https://dingyufan.github.io/posts/githubpages-hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/Sun, 10 Apr 2022 11:34:02 +0800 dingyufan https://dingyufan.github.io/posts/githubpages-hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/
\ No newline at end of file
diff --git a/tags/hugo/page/1/index.html b/tags/hugo/page/1/index.html
new file mode 100644
index 00000000..3796581d
--- /dev/null
+++ b/tags/hugo/page/1/index.html
@@ -0,0 +1 @@
+https://dingyufan.github.io/tags/hugo/
\ No newline at end of file
diff --git a/tags/index.html b/tags/index.html
new file mode 100644
index 00000000..c6587ca2
--- /dev/null
+++ b/tags/index.html
@@ -0,0 +1,4 @@
+所有标签 - dingyufan's blog
\ No newline at end of file
diff --git a/tags/index.xml b/tags/index.xml
new file mode 100644
index 00000000..40a10769
--- /dev/null
+++ b/tags/index.xml
@@ -0,0 +1 @@
+Tags - 标签 - dingyufan's blog https://dingyufan.github.io/tags/Tags - 标签 - dingyufan's blog Hugo -- gohugo.io zh-CN dingyufan@outlook.com (dingyufan) dingyufan@outlook.com (dingyufan) Sat, 09 Sep 2023 00:00:00 +0000 Clash https://dingyufan.github.io/tags/clash/Sat, 09 Sep 2023 00:00:00 +0000 dingyufan https://dingyufan.github.io/tags/clash/ Raspberry Pi https://dingyufan.github.io/tags/raspberry-pi/Sat, 09 Sep 2023 00:00:00 +0000 dingyufan https://dingyufan.github.io/tags/raspberry-pi/ Ubuntu https://dingyufan.github.io/tags/ubuntu/Sat, 09 Sep 2023 00:00:00 +0000 dingyufan https://dingyufan.github.io/tags/ubuntu/ Dubbo https://dingyufan.github.io/tags/dubbo/Sat, 30 Apr 2022 11:34:02 +0800 dingyufan https://dingyufan.github.io/tags/dubbo/ Java https://dingyufan.github.io/tags/java/Sat, 30 Apr 2022 11:34:02 +0800 dingyufan https://dingyufan.github.io/tags/java/ Github Pages https://dingyufan.github.io/tags/github-pages/Sun, 10 Apr 2022 11:34:02 +0800 dingyufan https://dingyufan.github.io/tags/github-pages/ Hugo https://dingyufan.github.io/tags/hugo/Sun, 10 Apr 2022 11:34:02 +0800 dingyufan https://dingyufan.github.io/tags/hugo/
\ No newline at end of file
diff --git a/tags/java/index.html b/tags/java/index.html
new file mode 100644
index 00000000..6a8d475a
--- /dev/null
+++ b/tags/java/index.html
@@ -0,0 +1,5 @@
+Java - 标签 - dingyufan's blog
\ No newline at end of file
diff --git a/tags/java/index.xml b/tags/java/index.xml
new file mode 100644
index 00000000..fdd0daa0
--- /dev/null
+++ b/tags/java/index.xml
@@ -0,0 +1 @@
+Java - 标签 - dingyufan's blog https://dingyufan.github.io/tags/java/Java - 标签 - dingyufan's blog Hugo -- gohugo.io zh-CN dingyufan@outlook.com (dingyufan) dingyufan@outlook.com (dingyufan) Sat, 30 Apr 2022 11:34:02 +0800 JDK8日期时间API导致Dubbo调用StackOverflow错误 https://dingyufan.github.io/posts/dubbo-stack-overflow-due-to-jdk8-instant/Sat, 30 Apr 2022 11:34:02 +0800 dingyufan https://dingyufan.github.io/posts/dubbo-stack-overflow-due-to-jdk8-instant/
\ No newline at end of file
diff --git a/tags/java/page/1/index.html b/tags/java/page/1/index.html
new file mode 100644
index 00000000..5bf6c41f
--- /dev/null
+++ b/tags/java/page/1/index.html
@@ -0,0 +1 @@
+https://dingyufan.github.io/tags/java/
\ No newline at end of file
diff --git a/tags/raspberry-pi/index.html b/tags/raspberry-pi/index.html
new file mode 100644
index 00000000..2c621766
--- /dev/null
+++ b/tags/raspberry-pi/index.html
@@ -0,0 +1,5 @@
+Raspberry Pi - 标签 - dingyufan's blog
\ No newline at end of file
diff --git a/tags/raspberry-pi/index.xml b/tags/raspberry-pi/index.xml
new file mode 100644
index 00000000..c5d08df3
--- /dev/null
+++ b/tags/raspberry-pi/index.xml
@@ -0,0 +1 @@
+Raspberry Pi - 标签 - dingyufan's blog https://dingyufan.github.io/tags/raspberry-pi/Raspberry Pi - 标签 - dingyufan's blog Hugo -- gohugo.io zh-CN dingyufan@outlook.com (dingyufan) dingyufan@outlook.com (dingyufan) Sat, 09 Sep 2023 00:00:00 +0000 浅尝树莓派4B(Ubuntu22.04)安装Clash作为代理服务器 https://dingyufan.github.io/posts/%E6%B5%85%E5%B0%9D%E6%A0%91%E8%8E%93%E6%B4%BE4bubuntu22.04%E5%AE%89%E8%A3%85clash%E4%BD%9C%E4%B8%BA%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/Sat, 09 Sep 2023 00:00:00 +0000 dingyufan https://dingyufan.github.io/posts/%E6%B5%85%E5%B0%9D%E6%A0%91%E8%8E%93%E6%B4%BE4bubuntu22.04%E5%AE%89%E8%A3%85clash%E4%BD%9C%E4%B8%BA%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/
\ No newline at end of file
diff --git a/tags/raspberry-pi/page/1/index.html b/tags/raspberry-pi/page/1/index.html
new file mode 100644
index 00000000..08297ace
--- /dev/null
+++ b/tags/raspberry-pi/page/1/index.html
@@ -0,0 +1 @@
+https://dingyufan.github.io/tags/raspberry-pi/
\ No newline at end of file
diff --git a/tags/ubuntu/index.html b/tags/ubuntu/index.html
new file mode 100644
index 00000000..306da797
--- /dev/null
+++ b/tags/ubuntu/index.html
@@ -0,0 +1,5 @@
+Ubuntu - 标签 - dingyufan's blog
\ No newline at end of file
diff --git a/tags/ubuntu/index.xml b/tags/ubuntu/index.xml
new file mode 100644
index 00000000..3c6e5dda
--- /dev/null
+++ b/tags/ubuntu/index.xml
@@ -0,0 +1 @@
+Ubuntu - 标签 - dingyufan's blog https://dingyufan.github.io/tags/ubuntu/Ubuntu - 标签 - dingyufan's blog Hugo -- gohugo.io zh-CN dingyufan@outlook.com (dingyufan) dingyufan@outlook.com (dingyufan) Sat, 09 Sep 2023 00:00:00 +0000 浅尝树莓派4B(Ubuntu22.04)安装Clash作为代理服务器 https://dingyufan.github.io/posts/%E6%B5%85%E5%B0%9D%E6%A0%91%E8%8E%93%E6%B4%BE4bubuntu22.04%E5%AE%89%E8%A3%85clash%E4%BD%9C%E4%B8%BA%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/Sat, 09 Sep 2023 00:00:00 +0000 dingyufan https://dingyufan.github.io/posts/%E6%B5%85%E5%B0%9D%E6%A0%91%E8%8E%93%E6%B4%BE4bubuntu22.04%E5%AE%89%E8%A3%85clash%E4%BD%9C%E4%B8%BA%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/
\ No newline at end of file
diff --git a/tags/ubuntu/page/1/index.html b/tags/ubuntu/page/1/index.html
new file mode 100644
index 00000000..6a56c786
--- /dev/null
+++ b/tags/ubuntu/page/1/index.html
@@ -0,0 +1 @@
+https://dingyufan.github.io/tags/ubuntu/
\ No newline at end of file