#656 正当防卫与互殴

2021-09-21

抖音上看到有人穿警服说,如果有人打你,你只要还手就是互殴,无关正当防卫。那普通人面对被打的情况该怎么正确处理?

#655 JWT 认证

2021-09-20

JWT 介绍

一句话介绍 JWT:就是通过非对称加密算法签名的方式,将部分用户信息存在客户端。

全名 JSON Web Token,简称 JWT。
内容 = 头 Headers (JSON) + 负载 Payload (JSON) + 签名 Signature
分别 Base64 编码之后,用 . 连接。

示例

JSON 实现

import base64

def jsonify(obj):
    return json.dumps(obj, ensure_ascii=False, separators=',:')

def base64urlEncoding(s):
    return base64.urlsafe_b64encode(s).rstrip(b'=').decode('ascii')

def sign(key, msg):
    return base64urlEncoding(hmac.new(key, msg, hashlib.sha256).digest())

# def verify(key, msg, sig):
#     return key.verify(msg, base64.urlsafe_b64decode(sig))

def e(obj):
    return base64urlEncoding(jsonify(header).encode('utf-8'))

def encode(key, header, payload):
    s = e(header) + '.' + e(payload)
    return s + '.' + sign(key, s)

def decode(token):
    header_str, payload_str, sig = token.split('.')
    header = json.loads(base64.urlsafe_b64decode(header_str).decode('utf-8'))
    payload = json.loads(base64.urlsafe_b64decode(payload_str).decode('utf-8'))
    sig = base64.urlsafe_b64decode(sig)

header = {
  "alg": "HS256",
  "typ": "JWT"
}
payload = {
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

signature = HMAC_SHA256(
    secret,
    base64urlEncoding(header) + '.' +
    base64urlEncoding(payload)
)
const token = base64urlEncoding(header) + '.' + base64urlEncoding(payload) + '.' + base64urlEncoding(signature)

标准字段

Claim 字段

Code Name Description
iss Issuer ..
sub Subject ..
aud Audience ..
exp Expiration Time ..
nbf Not Before ..
iat Issued At ..
jti JWT ID ..

Header 字段

Code Name Description
typ Type ..
cty Content Type ..
alg Algorithm ..
kid Key ID ..
x5c X.509 Certificate ..
x5u X.509 URL ..
x5t X.509 Thumbprint ..
jku JSON Web Key URL ..
jwk JSON Web Key ..
x5t#S256 X.509 SHA-256 Thumbprint ..

关于 JWT 的看法

  1. 相比 Session,减少了 DB/Redis 操作。
  2. 如果需要注销功能(踢下线),可以加一个黑名单,验证服务定时拉取这个黑名单
  3. 服务降级的时候,这一步可以去掉
  4. 相比加密 Cookie,不用依赖 HTTP Cookie,更加灵活,适用更多场景。
  5. 应该尽可能保证 Token 不要太长
  6. 顶多 4、5 个字段就够了
  7. JWT 和 加密 Cookie 一样,如果需要存储的信息太多,还是会让请求有点臃肿
  8. 大部分时候只需要验证身份,没有太多信息存储的需求,JWT
  9. 可以在网关那边统一校验
  10. 跨域

#654 GitHub 上那些徽章是怎么弄出来的?

2021-09-19

比如:Tornado 框架的 stars 数量:,再比如说这种:

外国人真是会玩,总有这样的好点子,在有限的环境(Markdown)中也能弄的丰富多彩的。

上面都是用的 shields.io 提供的服务,它内置了很多种类的图标,GitHub 关注数、Fork 数、Star 数、协议,Twitter 关注数、NPM 包大小、PyPI 下载数量等等,这些看他们官网提供的示例,照着用就是了。

还有一种,就是用户定制接口,格式:

  1. Static:URLPath https://img.shields.io/badge/<LABEL>-<MESSAGE>-<COLOR>
  2. Static:QueryString https://img.shields.io/static/v1?label=<LABEL>&message=<MESSAGE>&color=<COLOR>
  3. Endpoint https://img.shields.io/endpoint?url=<URL>&style<STYLE>
  4. Dynamic https://img.shields.io/badge/dynamic/json?url=<URL>&label=<LABEL>&query=<$.DATA.SUBDATA>&color=<COLOR>&prefix=<PREFIX>&suffix=<SUFFIX>

还有好多更加细致的规则,用来定制自己的图标,就不细说了,自己看文档就行。

其实我用的多的还是上面的第一种接口,因为它比较简单,只要把 label 和 message 修改就可以了。

https://img.shields.io/badge/site-markjour.com-brightgreen.svg?style=plastic&logo=nginx

https://img.shields.io/badge/-doing-green

参考资料与拓展阅读

#651 不简单的绝对值

2021-09-17

这篇文章讲到了绝对值计算的问题:One does not simply calculate the absolute value

IEEE 754

三个特殊值:

  1. 如果指数是0并且尾数的小数部分是0,这个数 ±0(和符号位相关)
  2. 如果指数 = 2^e - 1 并且尾数的小数部分是 0,这个数是 ±∞(同样和符号位相关)
  3. 如果指数 = 2^e - 1 并且尾数的小数部分非 0,这个数表示为非数(NaN)。

abs 的实现

class Test {
    public static double abs(double value) {
        if (value < 0) {
            return -value;
        }
        return value;
    }
    public static void main(String[] args) {
        double x = -0.0;
        if (1 / abs(x) < 0) {
            System.out.println("oops");
        }
    }
}

if 中加上条件:value == -0.0 是行不通的,因为 +0.0 == -0.0,可以使用 JDK 中的 Double.compare:

public static double abs(double value) {
    if (value < 0 || Double.compare(value, -0.0) == 0) {
        return -value;
    }
    return value;
}

这样确实有效,不过效率上可能会受到影响,abs 的复杂性就上了一个台阶。

JDK 17 中的实现

java/lang/Double.java

public static int compare(double d1, double d2) {
    if (d1 < d2)
        return -1;           // Neither val is NaN, thisVal is smaller
    if (d1 > d2)
        return 1;            // Neither val is NaN, thisVal is larger

    // Cannot use doubleToRawLongBits because of possibility of NaNs.
    long thisBits    = Double.doubleToLongBits(d1);
    long anotherBits = Double.doubleToLongBits(d2);

    return (thisBits == anotherBits ?  0 : // Values are equal
            (thisBits < anotherBits ? -1 : // (-0.0, 0.0) or (!NaN, NaN)
             1));                          // (0.0, -0.0) or (NaN, !NaN)
}

重新实现

参考 JDK 中的实现,重写 abs

private static final long MINUS_ZERO_LONG_BITS = Double.doubleToLongBits(-0.0);
public static double abs(double value) {
    if (value < 0 || Double.doubleToLongBits(value) == MINUS_ZERO_LONG_BITS) {
        return -value;
    }
    return value;
}

新的问题:NaN 的处理,处理方法:把 doubleToLongBits 改成 doubleToRawLongBits

private static final long MINUS_ZERO_LONG_BITS = Double.doubleToRawLongBits(-0.0);
public static double abs(double value) {
    if (value < 0 || Double.doubleToRawLongBits(value) == MINUS_ZERO_LONG_BITS) {
        return -value;
    }
    return value;
}

JVM 的 JIT 会替换这次调用为底层的 CPU 寄存器操作,效率非常可观。

PS:如果可以省去这个分支的判断逻辑,JVM 可以给我们更好的性能优化?

  1. 中间涉及 CPU 分支预测(branch predictor),如果预测错误,可能会付出相对昂贵的代码。

    We know that branches are bad. If the CPU branch predictor guesses incorrectly, they can be very expensive.

  2. 有传言说,这个调用(doubleToRawLongBits)会导致浮点数寄存器转换到通用集成器。

    Although there are rumors saying that this call may still lead to a transfer from a floating-point register to a general-purpose register. Still it's very fast.

进一步优化

采用 0 减负数等于正数,并且 0 - -0 = 0 的规则:

public static double abs(double value) {
    if (value <= 0) {
        return 0.0 - value;
    }
    return value;
}

这就是长期以来(直到最新的 Java 17),JDK 使用的方法(return (a <= 0.0D) ? 0.0D - a : a;)。

参考:JDK 17 中的的实现:java/lang/Math.java

再进一步

有人提出了意见,认为目前官方的实现 too slow(6506405: Math.abs(float) is slow #4711)。

这就是 jdk-18+6 中引入的新方案(java/lang/Math.java#L1600~L1604):

public static double abs(double a) {
    return Double.longBitsToDouble(Double.doubleToRawLongBits(a) & DoubleConsts.MAG_BIT_MASK);
}

DoubleConsts.MAG_BIT_MASK 就是 0x7fffffffffffffffL, 0 + 63 个 1。

原理就是,通过位运算,清除符号位(使之为 0)。

参考资料与拓展阅读

#650 《科技爱好者周刊(第 176 期)》阅读笔记

2021-09-17

视觉错觉

这么明显的视觉错觉,让人觉得好神奇!
我下次要拍一张这样的图片,拼在一起看看。
如果拍好了,我就更新在这个下面。

绝对值问题

由于有 +0 和 -0 的问题 (IEEE 754),问题变的复杂起来。

由于需要粘贴大量代码,我另起一篇吧(不简单的绝对值)!

手机学习网页开发

PS: 周刊中提供的链接打不开,我自己在 LinkedIn 上找到了这个用户,拿到了嵌入代码:

一个尼泊尔人教他弟弟学习 Web 开发的故事,由于没有电脑可用,就通过一部手机搭建了开发环境。
然后,经过两个月的学习,这个人成功的教他弟弟初步掌握 HTML、CSS 和 JS。

图片中采用的是什么 App 或者网站,我看不出来。但是阮一峰在上面提到了一个可以通过手机浏览器使用的 Web 开发环境 http://www.webden.dev,我试了一下,感觉还不错,如果接上鼠标键盘,确实可以用来学习。

宇宙飞船可以飞多远?

如果你走得足够远,其实就再也没办法回去了。当你回到故乡,原来的一切都已经消失了。
不过,这也不算什么,归根结底,旅程才是你的故事中最重要的部分。
-- 《如果有无尽的推动力,宇宙飞船可以飞多远?》

这段话让我想起 《三体》 中的一句话:“真正进入太空的人,再也不是人了”。
不过前者指的是时间流速不一致导致的错过,后者是社会意义上的,由于失去故土家园导致的,放弃之前所有的认同,包括国家、民族、价值观、甚至人的身份。

#649 自然语言处理(NLP)有关的几本书

2021-09-16

Python 自然语言处理

  1. 基于 NLTK

Python 自然语言处理实战

  1. 比较系统
  2. 大量使用 NumPy

NLP 汉语自然语言处理原理与实践

  1. 哈工大 LTP

精通 Python 自然语言处理

  1. 基于 NLTK

NLTK 基础教程:用 NLTK 和 Python 库构建机器学习应用