开发者 URI
2021-09-24
URI 与 URL
hierarchical part
┌───────────────────┴─────────────────────┐
authority path
┌───────────────┴───────────────┐┌───┴────┐
abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1
└┬┘ └───────┬───────┘ └────┬────┘ └┬┘ └─────────┬─────────┘ └──┬──┘
scheme user information host port query fragment
urn:example:mammal:monotreme:echidna
└┬┘ └──────────────┬───────────────┘
scheme path
URI
URI (统一资源标识符),标识一个资源主要有两种方法,根据名称(URN),根据路径(URL)。
URI 语法:

有一个问题就是注册的 URI scheme 基本上都可以认为是用于资源定位,所以 URI 和 URL 的概念就比较模糊了。
有的 scheme,比如说 data:<mediatype>[;base64],<data>,既不算 URL,也不算 URI。
有的 scheme,比如说 sms:<phone number>?<action>,我觉得是属于名称标识,但是不符合 URN 的定义(必须 urn:)。
而且实际上,就比如说 URL http://example.com/user/create,我们用来创建一个用户,如果套用万维网创建时的学院派设想,我们只能把创建这个动作理解成一个资源了。所以,我认为深入讨论这几个 “统一资源” 的概念之间到底存在什么差异没有什么意义,大致知道他们的渊源就够了:先随着 WWW 出来了 URL 的概念,然后又有人整出来一个 URN,再后来统一为 URI 标准,这几个概念本来就是混乱的,这就是现实生活,泾渭分明的概念只存在于那些教授们的理想中。
如果感兴趣可以参考 RFC 3305,这是一个 Infomational RFC,就是试图解释这几个概念,并向社区作出使用建议。
URL
URL (统一资源定位符) = 协议://认证信息@主机名:端口号/路径?查询字符串#片段
协议必须是在 IANA 注册的合法 URI scheme。
我们最熟悉的是 HTTP URL,协议是 http 或者 https,除此之外还有其他的 IANA 批准的协议,比如:
mailto:<address>[?<header1>=<value1>[&<header2>=<value2>]]
imap://[<user>[;AUTH=<type>]@]<host>[:<port>]/<command>
git://github.com/user/project-name.git
file://[host]/path or file:[//host]/path
view-source:<absolute-URI>
URN
URN (统一资源名称) is 资源名称(唯一标识),格式:urn:<NID>:<NSS>
urn 就是 IANA 注册的众多 URI scheme 之一。
-
这个命名空间标识 NID 也需要在 IANA 注册。
-
urn:isbn:0451450523
urn:ietf:rfc:2648
实际上,我好像没有见过什么地方在使用 URN,更别提设想中的统一资源属性 URC 了。
就好比上面提到的两个例子,ISBN: 0451450523,RFC 2648,直接这么写就是了,按照 RFC 定义写成标准的那一串实在是没见过。
总结
URL 是 URI 中的因特网部分,用来指示网络资源位置。
Python 示例
import urllib.parse
a = urllib.parse.urlunparse('http', 'user:pass@example.com:8080', '/path', 'param1=a', 'a=1&b=2', 'Title')
# 'http://user:pass@example.com:8080/path;param1=a?a=1&b=2#Title'
b = urllib.parse.urlparse(a)
b.scheme # 0 'http'
b.netloc # 1 'user:pass@example.com:8080'
b.path # 2 '/path'
b.params # 3 'param1=a'
b.query # 4 'a=1&b=2'
b.fragment # 5 'Title'
b.username # 'user'
b.password # 'pass'
b.hostname # 'example.com'
b.port # 8080
print(b[2]) # '/path'
b = urllib.parse.urlparse(('pymysql+mysql://root:111111@1.1.1.1:3306/xiaorui_master?charset=utf8mb4'))
print(repr((b.scheme, b.netloc, b.path, b.params, b.query, b.fragment, b.username, b.password, b.hostname, b.port)))
('pymysql+mysql', 'root:111111@1.1.1.1:3306', '/xiaorui_master', '', 'charset=utf8mb4', '', 'root', '111111', '1.1.1.1', 3306)
参考资料与拓展阅读
个人
2021-09-24
这些个常见的词到底有啥区别?
管理
2021-09-23
什么是授权?
所谓授权是指管理者把由他全权负责的一项或多项任务委派给下属员工,使下属拥有相当的自主权和行动权。
授权的6个误区
- 授权不是全程参与,授权后管理者不要过多干涉员工的工作;
- 授权不是弃权,授权是将任务、权利分配下去,再做适当的监督、帮助和支持;
- 授权不是授责,虽然把权利、工作内容和资源分配给下属,但是责任还是在主管身上;
- 授权不是代理职务,并不意味着下属拥有管理者的所有职权;
- 授权不是分工,分工在岗位说明中已经设定,但授权工作有可能在岗位职责之外;
- 授权不是助理,不是让下属打杂,而是要他独立完成一项工作。
管理者的四种不同授权风格
- 操控型:喜欢主动监督工作的进展,经常不说明理由,只是下令照办,并且严密监督工作的进行。适合于团队经验不足。
- 教练型:会密切监督下属工作的进行,但通常不会详细指点下属该如何进行,只会跟下属说明所交付的任务,逐步引导。当有必要或需要时,才会提供建议或支援。适合于新参加工作的员工。
- 顾问型:一般会给予执行工作的人更多的主导权,对于所交付的任务通常只是做大致上的描述,并且会征询对方的意见和观点,最终取得共识,最终接受任务的个人和团队投下更多的心力。这类管理者通常告知下属,只要表明需求,就会得到协助。
- 协调型:只给原则性的指示和注意事项,并表达进行协助的意愿,而做事的程序和方法全由下属自己决定。在授权时,会视工作执行的能力而给予相对的权限,日后再根据工作的进度进行调整。
下面举几个《三国演义》的例子
- 刘备谋赖孔明,武赖关张。孔明出山第一仗前,刘备命人取了佩剑和印信,代表了所有的权利都交给了孔明。孔明出计火烧博望坡时,张飞和关羽开始并不配合,刘备在旁边使眼色,关张二人不得不听令。
刘备在给孔明授权时表现了怎样的授权风格呢?
- 火烧博望坡之战,孔明给诸将下令:让关羽埋伏于豫山,让曹军先头部队过去,等南面火起,放火烧曹军粮草;让张飞埋伏于安林,等南面火起,烧曹军博望城之前屯粮的地方;让赵云在博望坡北负责诱敌深入。
孔明给关张赵三人授权时表现了怎样的授权风格呢?
- 赤壁之战火烧连营的真正实施者是黄盖,苦肉计前一天晚上,周瑜见到黄盖,首先询问黄盖的意见,黄盖自己提出用火攻。周瑜又引导说火攻实施的人很重要。这时候黄盖自告奋勇,愿意用苦肉计骗取信任,然后从曹军内部火攻。
周瑜给黄盖授权实施火烧连营的计策是怎样的授权风格呢?
- 东吴使美人计,实则想讨要荆州。孔明授权赵云保护刘备,并给了他三个锦囊,嘱咐他在紧急时刻依次拆看。
孔明给赵云授权时表现了怎样的授权风格呢?
答案
- 协调型
- 操控型
- 顾问型
- 教练型
影视 电影 歌曲
2021-09-22
《冰雪奇缘2》中的插曲。
个人 法律
2021-09-21
抖音上看到有人穿警服说,如果有人打你,你只要还手就是互殴,无关正当防卫。那普通人面对被打的情况该怎么正确处理?
JWT Auth 加密
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 的看法
- 相比 Session,减少了 DB/Redis 操作。
- 如果需要注销功能(踢下线),可以加一个黑名单,验证服务定时拉取这个黑名单
- 服务降级的时候,这一步可以去掉
- 相比加密 Cookie,不用依赖 HTTP Cookie,更加灵活,适用更多场景。
- 应该尽可能保证 Token 不要太长
- 顶多 4、5 个字段就够了
- JWT 和 加密 Cookie 一样,如果需要存储的信息太多,还是会让请求有点臃肿
- 大部分时候只需要验证身份,没有太多信息存储的需求,JWT
- 可以在网关那边统一校验
- 跨域
个人 开发者
2021-09-19
比如:Tornado 框架的 stars 数量:
、
,再比如说这种:
外国人真是会玩,总有这样的好点子,在有限的环境(Markdown)中也能弄的丰富多彩的。
上面都是用的 shields.io 提供的服务,它内置了很多种类的图标,GitHub 关注数、Fork 数、Star 数、协议,Twitter 关注数、NPM 包大小、PyPI 下载数量等等,这些看他们官网提供的示例,照着用就是了。
还有一种,就是用户定制接口,格式:
- Static:URLPath
https://img.shields.io/badge/<LABEL>-<MESSAGE>-<COLOR>
- Static:QueryString
https://img.shields.io/static/v1?label=<LABEL>&message=<MESSAGE>&color=<COLOR>
- Endpoint
https://img.shields.io/endpoint?url=<URL>&style<STYLE>
- 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

参考资料与拓展阅读
开发者 Windows11 TPM
2021-09-18
看新闻说 Windows 11 必须要 TPM 模块才能正常运行,虚拟机和物理机都一样。
我也是第一次听说这个东西。
DB 设计规范 编码风格
2021-09-18
随手写的,以命名为主(原来想写的是命名规范,结果写超了纲)。
开发者 Java
2021-09-17
这篇文章讲到了绝对值计算的问题:One does not simply calculate the absolute value。
IEEE 754
三个特殊值:
- 如果指数是0并且尾数的小数部分是0,这个数 ±0(和符号位相关)
- 如果指数 = 2^e - 1 并且尾数的小数部分是 0,这个数是 ±∞(同样和符号位相关)
- 如果指数 = 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 可以给我们更好的性能优化?
- 中间涉及 CPU 分支预测(branch predictor),如果预测错误,可能会付出相对昂贵的代码。
We know that branches are bad. If the CPU branch predictor guesses incorrectly, they can be very expensive.
- 有传言说,这个调用(
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)。
参考资料与拓展阅读