2020-05-05T00:00:00+08:00 https://github.com/bolasblack/BlogPosts/blob/master/_meta// c4605's blog c4605 bolasblack@gmail.com https://github.com/bolasblack TypeScript 如何真正 keyof 一个枚举 2020-05-05T00:00:00+08:00 2020-05-05T00:00:00+08:00 https://github.com/bolasblack/BlogPosts/blob/master/2020-05-05-TypeScript_如何真正_keyof_一个枚举 <p>有一个朋友问我怎么才能 <code>keyof</code> 一个 TypeScript 里的枚举,因为如果你直接 <code>keyof Enum</code> 的话,得到的结果是类似 <code>keyof number</code> ,而不是得到枚举的所有可能的 key 。</p> <p>于是我简单写了一下跟他解释,顺手发上来:</p> <p>在 TypeScript 里,你在声明一个枚举的时候,其实声明了两个类型,一个是枚举容器的类型(是一个对象),一个是枚举成员的类型(是数字/字符串或者其他类型的子类型)。</p> <p>TypeScript 的 <code>keyof</code> 操作符预设后面跟着的是类型,所以我们 <code>keyof Enum</code> 时,其实相当于是在 <code>keyof (作为 number 的子类型的 Enum)</code> ,也就是相当于 <code>keyof number</code> ,所以会得到 <code>number</code> 的属性和方法名。</p> <p>而 <code>typeof</code> 操作符预设后面跟的是一个值,所以当我们这么写 <code>keyof typeof Enum</code> 的时候,我们是把 <code>Enum</code> 作为一个值(也就是上文提到的“对象”),展开来以后就是 <code>keyof (作为类型的 Enum)</code> ,所以能够得到枚举的 key 。</p> <p>相关资料:</p> <ul> <li><a href="https://github.com/microsoft/TypeScript/issues/14106">keyof Enum - microsoft/TypeScript#14106 - GitHub</a></li> </ul> c4605 bolasblack@gmail.com https://github.com/bolasblack 尝试用 TypeScript 模拟一个带 Payload 的枚举 2020-05-02T17:28:03+08:00 2020-05-02T17:28:03+08:00 https://github.com/bolasblack/BlogPosts/blob/master/2020-05-01-尝试用_TypeScript_模拟一个带_Payload_的枚举 <p>这两天逛 GitHub 时突然又注意到了 <a href="https://github.com/origamitower/folktale">folktale</a> ,发现它现在带了一个 <a href="https://folktale.origamitower.com/api/v2.3.0/en/folktale.adt.union.union.union.html">adt/union</a> 。看起来 folktale 现在的 <code>Maybe</code>, <code>Result</code>, <code>Validation</code> 都是从 union 里派生出来的,使用方法和 Swift/Rust 里的枚举非常相似:</p> <pre><code class="language-javascript">const Either = union('Either', { Left: (value) =&gt; ({ value }), Right: (value) =&gt; ({ value }), }) console.log( Either.Right(1).matchWith({ Left: ({ value }) =&gt; `left ${value}`, Right: ({ value }) =&gt; `right ${value}`, }) ) </code></pre> <p>正好我想在 TypeScript 里模拟一个带 Payload 的枚举机制已经想了很久了,觉得这个 API 设计得不错,可惜的是没有 TypeScript 的类型定义,在各种抓耳挠腮之下写了一个不完美的版本(下面解释):</p> <pre><code class="language-typescript">// 这是一个假的函数,只是做个占位而已 export function union&lt;P extends BaseUnionPatterns&gt;( typeId: string, patterns: P, ): UnionTypes&lt;P&gt; { return null as any } export type UnionModelMatchPatterns&lt;P extends BaseUnionPatterns, R&gt; = { [K in keyof P]: (patternFnReturn: ReturnType&lt;P[K]&gt;) =&gt; R } export type UnionModelMatchPatternsWithAny&lt;P extends BaseUnionPatterns, R&gt; = { [K in keyof P]?: (patternFnReturn: ReturnType&lt;P[K]&gt;) =&gt; R } &amp; { $$any: (patternFnReturn: ReturnType&lt;P[keyof P]&gt;) =&gt; R } export type UnionInstance&lt;P extends BaseUnionPatterns, Type extends keyof P&gt; = { equals: (unionInstance: UnionInstance&lt;P, any&gt;) =&gt; boolean matchWith: &lt;R&gt;( branches: | UnionModelMatchPatterns&lt;P, R&gt; | UnionModelMatchPatternsWithAny&lt;P, R&gt;, ) =&gt; R } export interface UnionType&lt;P extends BaseUnionPatterns, K extends keyof P&gt; { (...args: Parameters&lt;P[K]&gt;): UnionInstance&lt;P, K&gt; hasInstance: (model: UnionInstance&lt;P, keyof P&gt;) =&gt; boolean } export type UnionTypes&lt;P extends BaseUnionPatterns&gt; = { [K in keyof P]: UnionType&lt;P, K&gt; } export interface BaseUnionPatterns { [type: string]: (...args: any) =&gt; any } </code></pre> <p>事情并不如愿,总归还是没有办法完全实现我想要的效果,最终的成效是:</p> <pre><code class="language-typescript">// 在这段代码里 // Maybe 变量的类型是对的 const Maybe = union('Maybe', { // patterns 参数的类型约束是对的 Nothing: () =&gt; null, Just: &lt;T&gt;(value: T) =&gt; ({ value }), Other: &lt;T&gt;(arg1: T, rest: T[]) =&gt; [arg1].concat(rest), }) // a 变量的类型是对的 const a = Maybe.Just(1) // b 变量的类型被成功推断 const b = a.matchWith({ // branches 的类型约束是对的 Nothing: (arg) =&gt; 'no value', // arg 的类型确实是 null Just: (arg) =&gt; `value: ${arg.value}`, // arg 的类型是 { value: unknown } (问题一) Other: (arg) =&gt; `values: ${arg.join(',')}`, // arg 的类型是 unknown[] (问题二) }) a.matchWith({ Just: (arg) =&gt; `value: ${arg.value}`, // TypeScript 里指定的 Symbol 没办法作为 key 的类型,所以只能用特殊 key $$any // 来替代 folktale 里的 any Symbol (问题三) $$any: (arg) =&gt; `values: ${arg}`, }) console.log(b) </code></pre> <p>我们来一个个看这些问题:</p> <ul> <li>关于问题一,让人比较不爽,毕竟 <code>Maybe.Just</code> 的参数类型是出现过的,只是因为 <code>patterns</code> 参数里的 <code>Just</code> 函数是一个泛型函数,而我们在做类型转换的时候又没办法传递类型参数,所以没办法推断出 <code>{ value: T }</code> 里 <code>T</code> 的类型;</li> <li>关于问题二,其实可以理解的,毕竟整个 <code>a</code> 变量的生命周期里从来没提到过 <code>Other</code> 的 Payload 类型;</li> <li>关于问题三,和问题一一样,都是属于语言设施功能上的缺位,大概只能等 TypeScript 支持相关的特性。</li> </ul> <p>后来结合问题一问题二一想,其实这两个问题可以尝试一起解决,我们只要在声明变量 <code>a</code> 的时候给它传递一个更完整的类型信息就可以了,于是又一阵抓耳挠腮之后,终于写出来一个粗糙的方案:</p> <pre><code class="language-typescript">export type UnionV&lt; T extends UnionTypes&lt;any&gt;, WrappedValues extends T extends UnionTypes&lt;infer P&gt; ? { [K in keyof P]: ReturnType&lt;P[K]&gt; } : never &gt; = UnionInstance&lt; { [K in keyof T]: (...args: Parameters&lt;T[K]&gt;) =&gt; WrappedValues[K] }, keyof T &gt; const Maybe = union('Maybe', { Nothing: () =&gt; null, Just: &lt;T&gt;(value: T) =&gt; ({ value }), Others: &lt;T&gt;(con: T, rest: T[]) =&gt; [con].concat(rest), }) type MaybeNumber = UnionV&lt; typeof Maybe, { // 这里填进去 patterns 各个函数的返回值类型 Nothing: null Just: { value: number } Others: number[] } &gt; const a = Maybe.Just('a') as MaybeNumber // 这回 b 的类型被成功推断为 number | null const b = a.matchWith({ Nothing: (arg) =&gt; arg, Just: (arg) =&gt; arg.value, // arg 的类型为 { value: number } Others: (items) =&gt; items.length, // arg 的类型为 number[] }) console.log(b) </code></pre> <p>目前这套方案在 <code>patterns</code> 的参数都是具体的类型时,不需要 <code>UnionV</code> 就能够工作得很好;在出现泛型函数时,我只能想到通过 <code>UnionV</code> 来为 tsc 提供额外的类型信息。</p> <p>那么,我们这算是大功告成了吗?并没有,因为整套方案是真的不够优雅,<code>UnionV</code> 的第二个类型参数实在是让人倒胃口。而且如果你仔细看的话,会发现我上面这段代码里 <code>Maybe.Just</code> 的参数是一个字符串,而 tsc 并没有报错。我知道是因为我用了转型,但是如果我这么写 <code>a: MaybeNumber =</code> ,tsc 会报错说 <code>Type '{ value: unknown; }' is not assignable to type '{ value: number; }'.</code> ,所以……这就是为什么这个方案不够优雅的原因之二了。</p> <p>目前还想不出来更好的方案,只能再等等了,看看 TypeScript 接下来会不会提供这套方案需要的东西吧。</p> c4605 bolasblack@gmail.com https://github.com/bolasblack 无固有尺寸的 SVG 图片在各浏览器中的渲染尺寸问题 2020-04-16T23:18:25+08:00 2020-04-16T23:18:25+08:00 https://github.com/bolasblack/BlogPosts/blob/master/2020-04-09-无固有尺寸的_SVG_图片在各浏览器中的渲染尺寸问题 <h2 id="故事">故事</h2> <p>公司同事在做图片预览功能时发现,很多 SVG 图片在 Firefox 下通过 <code>img</code> 标签加载成功后,<code>naturalWidth</code>/<code>naturalHeight</code> 是 0,和其他浏览器表现不同。</p> <p>这个事情让我有点好奇,按照经验,我觉得 Firefox 一般是最跟随标准的,但是它又何其他浏览器的实现都不相同。所以查了一些资料做了一些测试。经过简单测试后发现,没有 固有尺寸<sup id="fnref:intrinsic-dimensions"><a href="#fn:intrinsic-dimensions" class="footnote">1</a></sup> 的 SVG 在不同浏览器上使用 <code>img</code> 标签渲染时的尺寸是不同的。</p> <p>我用几个浏览器跑了一遍 <a href="https://codepen.io/AmeliaBR/pen/gvEJWr/ (&quot;natural&quot; dimensions of an SVG with viewBox only)">CodePen 上的例子</a>[^github-#3510]</p> <p>这个例子一共有四张图:</p> <ol> <li>有 <code>viewBox</code> 属性的正方图片</li> <li>有 <code>viewBox</code> 属性的非正方图片</li> <li>没有 <code>viewBox</code> 属性的非正方图片</li> <li>有 <code>viewBox</code> 和 <code>width</code>, <code>height</code> 属性的非正方图片</li> </ol> <p>分别在 Firefox v74, Chrome v80, Safari v13 里跑了一下代码:</p> <pre><code class="language-js">document.querySelectorAll('img').forEach(el =&gt; { console.log( 'width', el.width, 'height', el.height, 'naturalWidth', el.naturalWidth, 'naturalHeight', el.naturalHeight, ) }) </code></pre> <p>结果如下:</p> <ul> <li>Firefox v74: <ul> <li><code>img.naturalWidth</code>/<code>img.naturalHeight</code> <ul> <li>1, 2, 3 的这个属性都返回 0</li> <li>4 返回 SVG 图片 <code>width</code>, <code>height</code> 属性的值</li> </ul> </li> <li><code>img.width</code>/<code>img.height</code> <ul> <li>1, 2 的 <code>img.width</code> 都是 <code>100%</code> 的计算值,<code>img.height</code> 按比例缩放</li> <li>3 返回 <code>300x150</code></li> <li>4 返回 SVG 图片 <code>width</code>, <code>height</code> 属性的值</li> </ul> </li> </ul> </li> <li>Chrome v80 <ul> <li><code>img.naturalWidth</code>/<code>img.naturalHeight</code> <ul> <li>1, 2 的 <code>naturalHeight</code> 都返回 <code>150</code> ,<code>naturalWidth</code> 按比例缩放</li> <li>3 返回 <code>300x150</code></li> <li>4 返回 SVG 图片 <code>width</code>, <code>height</code> 属性的值</li> </ul> </li> <li><code>img.width</code>/<code>img.height</code> 和 Firefox 表现一致</li> </ul> </li> <li>Safari v13 <ul> <li><code>img.naturalWidth</code>/<code>img.naturalHeight</code> <ul> <li>1, 2 的 <code>naturalWidth</code> 都返回 <code>100%</code> 的计算值,<code>naturalHeight</code> 按比例缩放</li> <li>3 返回 <code>300x150</code></li> <li>4 返回 SVG 图片 <code>width</code>, <code>height</code> 属性的值</li> </ul> </li> <li><code>img.width</code>/<code>img.height</code> 和 Firefox 表现一致</li> </ul> </li> </ul> <p>然后我查了一下 <a href="https://html.spec.whatwg.org/commit-snapshots/a027acbded508ca06c67fbc9e550e22072681463/#dom-img-naturalwidth">2020-04-09 时的 HTML 的标准对 <code>naturalWidth</code> 的描述</a>:</p> <blockquote> <p>The IDL attributes <code>naturalWidth</code> and <code>naturalHeight</code> must return the density-corrected intrinsic width and height of the image, in CSS pixels, if the image has intrinsic dimensions and is available, or else 0.</p> </blockquote> <p>看起来好像是 Firefox 的实现比较贴近当前的 HTML 标准。</p> <h2 id="那么如果我想体面地渲染一张随机的-svg-图片我该怎么办呢">那么如果我想体面地渲染一张随机的 SVG 图片,我该怎么办呢?</h2> <p>目前我没有想到什么特别好的方案,可能会需要自己根据需求来确定方案</p> <ul> <li>如果可以获取到 SVG 文件的代码,而且可以执行 JavaScript ,那么我们可以通过判断 <code>&lt;svg&gt;</code> 标签有没有 <code>width</code>, <code>height</code> 来确认图片是否有固有尺寸,有的话就直接使用 <code>naturalWidth</code>/<code>naturalHeight</code> ,没有的话就使用一 <code>img.width</code>/<code>img.height</code> 计算出宽高比,然后使用我们预置的宽度/高度来渲染图片,另外一边等比缩放</li> <li>如果没办法获取 SVG 文件的代码,或者没办法执行 JavaScript ,那么我们就只能给图片的外部套一个限制了宽高的容器了。如果图片有固有尺寸,那么图片会按照固有尺寸进行渲染(如果超出了我们限制的宽高它会等比缩放);如果没有固有尺寸,它就会自动缩放以适应我们限制的宽高</li> </ul> <h2 id="其他资料">其他资料</h2> <ul> <li><a href="https://thatemil.com/blog/2014/04/06/intrinsic-sizing-of-svg-in-responsive-web-design/">Intrinsic sizing of SVG in responsive web design - That Emil.</a></li> </ul> <div class="footnotes"> <ol> <li id="fn:intrinsic-dimensions"> <p>固有尺寸(Intrinsic Dimensions)在 CSS 里指的是 object 内置的 偏好(preferred)/原生(natural) 的尺寸(the intrinsic height, intrinsic width, and intrinsic aspect ratio),CSS 标准里没有定义如何获取这部分信息。而在 SVG 图片这个上下文里,我们可以认为 SVG 图片的 <code>width</code>/<code>height</code> 属性就是它的固有尺寸。 https://drafts.csswg.org/css-images-3/#intrinsic-dimensions <a href="#fnref:intrinsic-dimensions" class="reversefootnote">&#8617;</a></p> </li> </ol> </div> c4605 bolasblack@gmail.com https://github.com/bolasblack 关于 React 和 Angular 的看法 2016-11-28T00:40:41+08:00 2016-11-28T00:40:41+08:00 https://github.com/bolasblack/BlogPosts/blob/master/2015-06-03-关于_React_和_Angular_的看法 <p>我最近也用了一段时间的 reactjs ,所以我想有还是稍微有点资格来谈论 angularjs 和 reactjs 的。</p> <p>有一些文章会提到 angularjs 的两个很关键的问题:controller 拥有了自己的状态,和声明依赖比较容易出现循环依赖。</p> <p>Facebook 确实提出了一个非常不错的解决方案,但这方案与 Virtual DOM 没有直接的关系(一些文章总是提这东西,我承认这是一个很重要的特性,但和解决 angularjs 的问题关系不大)。</p> <p>这个方案是 <a href="http://facebook.github.io/flux/">Flux</a> :通过发布事件的方式来提醒模型更新状态,进而更新所有相关 DOM ,一个很巧妙的解决方案。而 Virtual DOM 只是这个解决方案里保证性能的一环而已。</p> <p>而 data-binding ,实话说看到一些 reactjs 的拥趸一天到晚扯 data-binding 是邪恶的让我觉得很莫名其妙。data-binding 只是一种模式而已,reactjs 内置了数据的单向绑定难道这就不是 data-binding ?而且 reactjs 还带了一个扩展 <a href="http://facebook.github.io/react/docs/two-way-binding-helpers.html">ReactLink</a> 用来实现数据的双向绑定又是什么意思?难道在 Vitrual DOM 的 <code>onChange</code> 属性里传一个回调函数然后更新 <code>state</code> 或者发出事件更新模型就不是数据绑定了?那么其他框架实现双向绑定的方法 “监听 DOM 事件,更新 ViewModel” 也不能算是双向绑定咯?因为这其实和在 reactjs 里做的没有任何区别。</p> <p>我的观点就是,reactjs 是一个不错的东西,Flux 解决了不少其他 MVVM 框架的问题,而 data-binding 绝对是一种进步,因为它大量的减少了开发者的代码量(早就有人 <a href="https://github.com/spoike/refluxjs#using-refluxconnect">实现</a> 了一个使用 flux 模式的双向绑定扩展了)。</p> c4605 bolasblack@gmail.com https://github.com/bolasblack OS X 下推荐的一些命令行工具 2015-04-21T10:31:14+08:00 2015-04-21T10:31:14+08:00 https://github.com/bolasblack/BlogPosts/blob/master/2015-04-21-OS_X_下一推荐的一些命令行工具 <p>太常见的我就不记录了:</p> <ul> <li><a href="http://brew.sh/">homebrew</a> 这个不用多作介绍,之所以列出来主要是为了列一些扩展工具 <ul> <li><a href="https://github.com/gapple/homebrew-services">homebrew services</a> 一个勉强算是 OS X 下的 <code>/etc/init.d</code> ,一开始 <code>brew services list</code> 的时候是什么都没有的,你得手动 <code>brew services start nginx</code> 这样子才算是正是把它交给 homebrew services 管理。虽然有点蛋疼,但不管怎么说,有总比没有强,是吧?</li> <li><a href="https://github.com/beeftornado/homebrew-rmtree">homebrew-rmtree</a> 删除指定包的时候顺手删除没有被别的包用到的依赖,其实就是帮你做了 <code>brew cleanup package</code> 和 <code>brew rm --force package \</code>join &lt;(brew leaves) &lt;(brew deps package)`` 的工作</li> </ul> </li> <li><a href="https://github.com/junegunn/fzf">fzf</a> 一个可扩展的模糊查询工具,可以过滤历史命令、<code>kill -9</code> 进程名称、<code>ssh</code> 的帐号和服务器地址什么的。也支持作为 Vim 的扩展来过滤命令什么的</li> <li><a href="https://github.com/github/hub">hub</a> 算是 git 的扩展吧,很好的把 GitHub 和 git 命令融合了起来,比如 <code>git create reponame</code> 或者 <code>git clone reponame</code> 什么的,开坑方便了不少</li> <li>ffmpeg 神器不用多作介绍,不过以前年轻不懂事,一直不怎么有在意这个,最近终于知道这玩意有多好用了,所以这里还是要列一下</li> <li>aria2c 如上</li> <li>mosh 嗯,如果到服务器的连接不稳定,那就用这个吧,反正我之前连 linode 日本的服务器一天到晚断线,用了这个再也没断过了</li> <li>coreutils GNU 的那套命令行工具比如 <code>ls</code> ,因为 OS X 的工具很多都是 BSD 系的,很多参数和 GNU 系的不一样,但很多时候我们自己写的脚本需要到 Linux 下面去跑,所以干脆还是直接在 OS X 下也用 GNU 的那套工具得了</li> <li><a href="https://github.com/ChrisJohnsen/tmux-MacOSX-pasteboard">reattach-to-user-namespace</a> 这个算是比较蛋疼,是为了解决在 tmux 下无法将文本复制到系统剪贴板的问题</li> </ul> c4605 bolasblack@gmail.com https://github.com/bolasblack Gentoo 上部署 ocserv 2015-04-12T00:43:30+08:00 2015-01-30T22:08:08+08:00 https://github.com/bolasblack/BlogPosts/blob/master/2015-01-30-Gentoo_上部署_ocserv <p>终于忍受不了 iOS 上翻墙的蛋疼劲了,VPN 一锁屏就断。和废掉完全没区别。所以,查查资料,我们来装 ocserv 吧。</p> <h2 id="安装-gnutls">安装 GnuTLS</h2> <pre><code class="language-bash">sudo emerge -av gnutls </code></pre> <h2 id="安装-ocserv">安装 ocserv</h2> <p>这个教程一抓一大吧,而且问题似乎都不大。</p> <pre><code class="language-bash">cd ~ wget ftp://ftp.infradead.org/pub/ocserv/ocserv-0.10.2.tar.xz tar xvf ocserv-0.10.2.tar.xz cd ocserv-0.10.2 ./configure make -j2 &amp;&amp; sudo make install </code></pre> <p>这样子就编译安装完成了。接下来配置 ocserv 。</p> <h2 id="配置-ocserv">配置 ocserv</h2> <p>先建证书:</p> <pre><code class="language-bash">sudo mkdir -p /etc/ocserv/certificates cd /etc/ocserv/certificates </code></pre> <p>创建 <code>ca.tmpl</code> 模板,这里的 <code>cn</code> 和 <code>organization</code> 可以随便写:</p> <pre><code>cn = "Your CA name" organization = "Your fancy name" serial = 1 expiration_days = 3650 ca signing_key cert_signing_key crl_signing_key </code></pre> <p>创建 <code>server.tmpl</code> 模板,这里的 <code>cn</code> 必须对应最终提供服务的 hostname 或 IP :</p> <pre><code>cn = "Your hostname or IP" organization = "Your fancy name" expiration_days = 3650 signing_key encryption_key tls_www_server </code></pre> <p>然后来建证书:</p> <pre><code class="language-bash">sudo certtool --generate-privkey --outfile ca-key.pem sudo certtool --generate-privkey --outfile server-key.pem sudo certtool --generate-self-signed --load-privkey ca-key.pem --template ca.tmpl --outfile ca-cert.pem sudo certtool --generate-certificate --load-privkey server-key.pem --load-ca-certificate ca-cert.pem --load-ca-privkey ca-key.pem --template server.tmpl --outfile server-cert.pem </code></pre> <p>然后来改配置:</p> <pre><code class="language-bash">sudo cp ~/ocserv-0.10.2/doc/sample.config /etc/ocserv/ocserv.conf sudo vim /etc/ocserv/ocserv.conf </code></pre> <p>文件内容:</p> <pre><code class="language-conf"># 登陆方式,目前先用密码登录 auth = "plain[/etc/ocserv/ocpasswd]" # 允许同时连接的客户端数量 max-clients = 4 # 限制同一客户端的并行登陆数量 max-same-clients = 2 # 服务监听的IP(服务器 IP ,可不设置) listen-host = 1.2.3.4 # 服务监听的 TCP/UDP 端口,这个自己看着办,客户端以 IP:PORT 的格式来连接 # 如果改了的话两个端口最好不同,我在使用时发现如果端口相同的话,会导致请求被阻塞的情况 tcp-port = 9000 udp-port = 9001 # 自动优化 VPN 的网络性能 try-mtu-discovery = true # 服务器证书与密钥 server-cert = /etc/ocserv/certificates/server-cert.pem server-key = /etc/ocserv/certificates/server-key.pem # 客户端连上 VPN 后使用的 DNS dns = 8.8.8.8 # 注释掉所有的 route ,让服务器成为 gateway #route = 192.168.1.0/255.255.255.0 # 启用 cisco 客户端兼容性支持 cisco-client-compat = true # 开着这个会报错:error: 'isolate-workers' is set to true, but not compiled with seccomp or Linux namespaces support # 好像是内核不支持,反正自己看着办 isolate-workers = false </code></pre> <p>最后生成帐号密码文件:</p> <pre><code>sudo ocpasswd -c /etc/ocserv/ocpasswd username </code></pre> <h2 id="其他配置">其他配置</h2> <p>以 <a href="https://www.linode.com/docs/security/securing-your-server#creating-a-firewall">Linode 的配置</a> 为例,新建或修改 <code>/etc/iptables.firewall.rules</code> 文件:</p> <pre><code class="language-iptables"># 如果是新建文件才需要这行 *filter # 这里的端口填 ocserv 配置里的 tcp-port 和 udp-port -A INPUT -p tcp -m state --state NEW --dport 9000 -j ACCEPT -A INPUT -p udp -m state --state NEW --dport 9001 -j ACCEPT # 注释这行,允许转发 # -A FORWARD -j DROP # 如果是新建文件才需要这行 COMMIT #启用NAT *nat -A POSTROUTING -j MASQUERADE COMMIT </code></pre> <p>完成之后导入新配置并检查配置正确:</p> <pre><code class="language-bash">sudo iptables-restore &lt; /etc/iptables.firewall.rules sudo iptables -L sudo iptables -t nat -L </code></pre> <p>接着打开 IPv4 的流量转发:</p> <pre><code>sudo vim /etc/sysctl.conf </code></pre> <p>启用此项:</p> <pre><code>net.ipv4.ip_forward=1 </code></pre> <p>刷新配置:</p> <pre><code>sudo sysctl -p /etc/sysctl.conf </code></pre> <h2 id="测试一下">测试一下</h2> <pre><code>sudo ocserv -f -d 1 </code></pre> <p>如果运行成功,就下载一个 AnyConnect 客户端来测试一下。</p> <p>如果证书是自己签发的,那么 iOS 客户端在连接前先到 <code>Settings</code> 标签页关闭 <code>Block Untrusted Servers</code> 。</p> <h2 id="troubleshooting">Troubleshooting</h2> <h3 id="ocserv-error-while-loading-shared-libraries-libgnutlsso28-cannot-open-shared-object-file-no-such-file-or-directory">ocserv: error while loading shared libraries: libgnutls.so.28: cannot open shared object file: No such file or directory</h3> <p>启动 ocserv 的命令改一下:</p> <pre><code>sudo LD_LIBRARY_PATH=/lib:/usr/lib:/usr/local/lib ocserv -f -d 1 </code></pre> <h3 id="无法访问国内网站">无法访问国内网站</h3> <p>错误信息:</p> <pre><code>ocserv[15995]: main: 客户端IP:1035: unexpected DTLS content type: 23; possibly a firewall disassociated a UDP session </code></pre> <p>给 <code>/etc/ocserv/ocserv.conf</code> 加两个路由:</p> <pre><code>route = 0.0.0.0/128.0.0.0 route = 128.0.0.0/128.0.0.0 </code></pre> <h3 id="无法访问外网">无法访问外网</h3> <p>去设置一下 iptables ,我被这个坑两次了。</p> <h2 id="余话">余话</h2> <p>关于开机自动启动 ocserv ,开机自动载入 iptables 配置,客户端证书自动连接,这些东西我就不在这里写了,可以看下面的参考文章。</p> <p>我建了一个 Gist :Gentoo 的 ocserv 启动脚本:https://gist.github.com/bolasblack/9f53b048e46f538cf08d</p> <p>记得把 <code>PIDFILE</code> 的路径改成 <code>/etc/ocserv/ocserv.conf</code> 里配置的 <code>pid-file</code> 路径。</p> <p>最后,祝 GFW 早日被终结。</p> <p>参考文章:</p> <ul> <li><a href="http://bitinn.net/11084/">折腾笔记:架设OpenConnect Server给iPhone提供更顺畅的网络生活</a></li> <li><a href="http://blog.ihipop.info/2014/07/4782.html">Gentoo编译安装Ocserv上Cisco AnyConnect VPN</a></li> <li><a href="http://www.bauer-power.net/2014/06/how-to-install-gnutls-3123-from-source.html">HOW TO INSTALL GNUTLS 3.1.23 FROM SOURCE IN UBUNTU 14.04</a></li> </ul> c4605 bolasblack@gmail.com https://github.com/bolasblack Gentoo 上安装 Jenkins 的若干个坑 2015-04-09T17:52:38+08:00 2014-09-04T13:09:07+08:00 https://github.com/bolasblack/BlogPosts/blob/master/2014-09-04-Gentoo_上安装_Jenkins_的若干个坑 <p>嗯,在 Gentoo 上装了一下 Jenkins ,记录一下遇到的坑吧。</p> <h2 id="overlay">Overlay</h2> <p><del>Gentoo 官方的 portage 和 gentoo-zh 里都是没有 Jenkins 的,所以得自己去找一下 overlay 。我在 <a href="http://gpo.zugaina.org/">http://gpo.zugaina.org/</a> 这个网站找到了相应的<a href="http://gpo.zugaina.org/">文件</a> ,然后由于下文中会提到的一些事情,所以对文件做了一些改动,push 到了自己的仓库 <a href="https://github.com/bolasblack/overlay">bolasblack/overlay</a> 。版本是 1.577 ,官方最新是 1.578 ,不过看了一下 changelog 似乎没啥太大的变化,就懒得写了。</del></p> <p>Jenkins 现在已经进入到了官方的 portage 里了,可以使用 <code>emerge -av jenkins-bin</code> 来安装。</p> <h2 id="部署上的一些零碎">部署上的一些零碎</h2> <p>Gentoo 的 <a href="http://www.gentoo.org/doc/en/handbook/handbook-x86.xml?part=2&amp;chap=4">Init Scripts</a> 配置文件一般都在 <code>/etc/conf.d/servicename</code> 里面, Jenkins 也不例外,改监听的端口什么的都去那边改。</p> <p>虽然过了一年半了, Nginx 的配置文件还是可以参考 Jenkins 的 Wiki 的:<a href="https://wiki.jenkins-ci.org/display/JENKINS/Running+Hudson+behind+Nginx">https://wiki.jenkins-ci.org/display/JENKINS/Running+Hudson+behind+Nginx</a>。</p> <p>这里有一份简化了感觉好像还能用的配置文件,就是不知道什么时候会不会遇到坑 <a href="https://gist.github.com/bolasblack/578d9f086a93ab69bf3b#file-jenkins-conf-L5">https://gist.github.com/bolasblack/578d9f086a93ab69bf3b</a> 。</p> <h2 id="no-valid-crumb-was-included-in-the-request">No valid crumb was included in the request</h2> <p>如果在点击某些选项后页面上什么都没出来,JS 控制台提示某个 POST 请求被响应 403 ,Status Text 是标题的话,那就是你在全局安全设置里开了“防止跨站点请求伪造”,然后被 Nginx/Apache 坑了。</p> <p>Apache 我只知道要禁用 <code>ignore_invalid_headers</code> 。Nginx 的话要在 <code>server</code> 块里添加一行 <code>ignore_invalid_headers off;</code> 来防止 Nginx 过滤掉 Jenkins 为了防止 CSRF 发出的 <code>.crumb</code> 或者 <code>Crumb</code> 请求头。</p> <p>示例代码:<a href="https://gist.github.com/bolasblack/578d9f086a93ab69bf3b#file-jenkins-conf-L5">https://gist.github.com/bolasblack/578d9f086a93ab69bf3b#file-jenkins-conf-L5</a></p> <p>参考链接:</p> <ul> <li>http://www.myexception.cn/ruby-rails/1617396.html</li> <li>https://issues.jenkins-ci.org/browse/JENKINS-12875</li> </ul> <h2 id="git-commit-乱码">Git commit 乱码</h2> <p>具体的处理方法是在系统变量 <code>JAVA_TOOL_OPTIONS</code> 里设置默认编码,我们需要的就是 <code>-Dfile.encoding=UTF-8</code> 。</p> <p>参考链接:</p> <ul> <li>http://bbbush.livejournal.com/392149.html</li> <li>http://www.tuicool.com/articles/f6J3I3</li> </ul> <h2 id="ssh-username-with-private-key">SSH Username with private key</h2> <p>这个 key Jenkins 是不会自动创建的,需要你手动执行 <code>sudo -u jenkins ssh-keygen -t rsa</code> 来新建。</p> <p>由于官方的 conf.d 里把 <code>jenkins</code> 的家目录默认设置在了 <code>/var/lib/jenkins</code> ,所以找 ssh 的 pub key 的时候也要去那个目录下找。</p> <h2 id="it-appears-that-your-reverse-proxy-set-up-is-broken">It appears that your reverse proxy set up is broken</h2> <p>我也不知道怎么解决,反正用了我自己那个 nginx 配置文件以后就解决了</p> <p>可能有帮助的链接(反正我没得到帮助):</p> <ul> <li>http://www.phase2technology.com/blog/running-jenkins-behind-nginx/</li> </ul> <h2 id="设置启用安全后所有页面都需要-basic-http-authorization">设置“启用安全”后所有页面都需要 Basic HTTP Authorization</h2> <p>尝试一下在 <code>/var/lib/jenkins/home/config.xml</code> 文件保持 <code>&lt;useSecurity&gt;</code> 的情况下删除 <code>&lt;authorizationStrategy&gt;</code> 和 <code>&lt;securityRealm&gt;</code> ,然后重新激活“启用安全”。</p> <p>参考链接:</p> <ul> <li>http://stackoverflow.com/questions/29530558/how-to-disable-basic-http-auth-of-jenkins</li> </ul> <h2 id="如何设置-jenkins-监听的地址">如何设置 Jenkins 监听的地址</h2> <p>修改 <code>/etc/conf.d/jenkins</code> 文件的 <code>JENKINS_ARGS="--httpListenAddress=127.0.0.1"</code> 来把监听地址设置为 <code>127.0.0.1</code> 。</p> <p>也可以通过命令 <code>java -jar /opt/jenkins/jenkins.war --help</code> 来获取其他配置参数。</p> c4605 bolasblack@gmail.com https://github.com/bolasblack Audio 标签的若干个坑 2014-08-22T14:51:58+08:00 2014-08-22T14:51:58+08:00 https://github.com/bolasblack/BlogPosts/blob/master/2014-08-21-Audio_标签的若干个坑 <p>这两天写了一点和 <code>Audio</code> 标签相关的代码,于是就被浏览器坑的昏天暗地,在这里做个笔记</p> <h2 id="状态码">状态码</h2> <p>Chrome, Safari, Firefox 实现的 <code>Audio</code> 标签是不支持对响应状态码为 <code>200</code> 的文件进行循环的,目前看起来这似乎已经变成了行业的潜规则似的。说是潜规则,是因为我没有在规范里找到任何与此有关的描述,我没有对 IE 进行测试。这里也有人遇到了和我一样的问题:<a href="http://stackoverflow.com/questions/8088364/html5-video-will-not-loop">HTML5 video will not loop</a>。</p> <h2 id="audio-标签的-load-方法">Audio 标签的 load 方法</h2> <p>在 30/31 版本的 Firefox 中 <code>audio.load()</code> 方法是基本没什么作用的(我没有测试更低版本的), 如果一个 <code>Audio</code> 标签 <code>preload</code> 属性的值为 <code>none</code> ,那么它不会自动地去加载这个音频文件,在其他浏览器中可以通过 <code>audio.load()</code> 来触发下载音频文件的行为进而实现按需预加载的效果,但这在 Firefox 下是无效的,只有调用 <code>audio.play()</code> 时它才会遵循<a href="http://www.w3.org/TR/html5/embedded-content-0.html#playing-the-media-resource">规范</a>触发下载资源的行为</p> <h2 id="循环播放的间隔">循环播放的间隔</h2> <p>不同浏览器实现的循环播放的间隔是不同的,测试代码:</p> <iframe width="100%" height="300" src="http://jsfiddle.net/kfpyk5qk/embedded/" allowfullscreen="allowfullscreen" frameborder="0"></iframe> <p>结果如下:</p> <pre><code># Chrome - 1 1161 # Chrome - 2 10215 # Firefox - 1 1264 # Firefox - 2 10280 # 最令人发指的 Safari - 1 2324 # 最令人发指的 Safari - 2 11330 </code></pre> <p>目前没有找到比较好的办法来处理这个问题。</p> c4605 bolasblack@gmail.com https://github.com/bolasblack Socket.io 如何切换 Namespace 和 querystring 2014-07-01T23:29:20+08:00 2014-07-01T23:29:20+08:00 https://github.com/bolasblack/BlogPosts/blob/master/2014-07-01-Socket.io_如何切换_Namespace_和_querystring <p>目前的 Socket.io 的情况似乎是,当我发起过一次连接后,接下来的所有新连接的 namespace 和 query 等内容都会使用第一次的配置,蛋疼无比。</p> <p>看了下代码,然后又尝试了一些时间,找出了办法:</p> <pre><code class="language-javascript">var socket = io.connect('/path', {query: {key: 'value'}}) // 当需要切换的时候 socket.close() socket.io.opts.query.key = '...' // 设置 querystring socket.io.opts.path = '/newpath' // 切换 namespace socket.open() </code></pre> c4605 bolasblack@gmail.com https://github.com/bolasblack 为什么我喜欢在 Terminal 里使用 Vim 2014-05-09T17:48:53+08:00 2014-05-09T17:48:53+08:00 https://github.com/bolasblack/BlogPosts/blob/master/2013-02-24-为什么我喜欢在_Terminal_里使用_Vim <p>其实很简单,因为 Shell 好用~</p> <p>在 Terminal 打开 Vim ,然后 <kbd>Ctrl-z</kbd> 。</p> <p>“biu!”,不见了。</p> <p>再开个 node ,然后 <kbd>Ctrl-z</kbd> 。</p> <p>“biu!”,又不见了。</p> <p>然后输入 <code>jobs</code> ,回车:</p> <pre><code>[1] - suspended vim [2] + suspended node </code></pre> <p>然后输入 <code>fg %2</code> ,回车,“biu!”,Vim 又出来了。</p> <p>退出 Vim ,然后输入 <code>fg</code> ,“biu!”,node 回来了。</p> <p>什么?你说 Terminal 下复制麻烦?</p> <pre><code class="language-vimscript">:'&lt;,'&gt;w !pbcopy&lt;CR&gt; </code></pre> c4605 bolasblack@gmail.com https://github.com/bolasblack