Java手撸HttpClient

先将代码贴上,后面再整理下

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
import javax.net.ssl.SSLSocketFactory;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.UUID;

public class HttpTest {

private static final String SPLIT = "\r\n";

private static final String RAW_BODY = """
豫章故郡,洪都新府。星分翼轸,地接衡庐。襟三江而带五湖,控蛮荆而引瓯越。物华天宝,龙光射牛斗之墟;人杰地灵,徐孺下陈蕃之榻。雄州雾列,俊采星驰。台隍枕夷夏之交,宾主尽东南之美。都督阎公之雅望,棨戟遥临;宇文新州之懿范,襜帷暂驻。十旬休假,胜友如云;千里逢迎,高朋满座。腾蛟起凤,孟学士之词宗;紫电青霜,王将军之武库。家君作宰,路出名区;童子何知,躬逢胜饯。
时维九月,序属三秋。潦水尽而寒潭清,烟光凝而暮山紫。俨骖騑于上路,访风景于崇阿;临帝子之长洲,得天人之旧馆。层峦耸翠,上出重霄;飞阁流丹,下临无地。鹤汀凫渚,穷岛屿之萦回;桂殿兰宫,即冈峦之体势。
披绣闼,俯雕甍,山原旷其盈视,川泽纡其骇瞩。闾阎扑地,钟鸣鼎食之家;舸舰弥津,青雀黄龙之舳。云销雨霁,彩彻区明。落霞与孤鹜齐飞,秋水共长天一色。渔舟唱晚,响穷彭蠡之滨;雁阵惊寒,声断衡阳之浦。
遥襟甫畅,逸兴遄飞。爽籁发而清风生,纤歌凝而白云遏。睢园绿竹,气凌彭泽之樽;邺水朱华,光照临川之笔。四美具,二难并。穷睇眄于中天,极娱游于暇日。天高地迥,觉宇宙之无穷;兴尽悲来,识盈虚之有数。望长安于日下,目吴会于云间。地势极而南溟深,天柱高而北辰远。关山难越,谁悲失路之人?萍水相逢,尽是他乡之客。怀帝阍而不见,奉宣室以何年?
嗟乎!时运不齐,命途多舛。冯唐易老,李广难封。屈贾谊于长沙,非无圣主;窜梁鸿于海曲,岂乏明时?所赖君子见机,达人知命。老当益壮,宁移白首之心?穷且益坚,不坠青云之志。酌贪泉而觉爽,处涸辙以犹欢。北海虽赊,扶摇可接;东隅已逝,桑榆非晚。孟尝高洁,空余报国之情;阮籍猖狂,岂效穷途之哭!
勃,三尺微命,一介书生。无路请缨,等终军之弱冠;有怀投笔,慕宗悫之长风。舍簪笏于百龄,奉晨昏于万里。非谢家之宝树,接孟氏之芳邻。他日趋庭,叨陪鲤对;今兹捧袂,喜托龙门。杨意不逢,抚凌云而自惜;钟期既遇,奏流水以何惭?
呜乎!胜地不常,盛筵难再;兰亭已矣,梓泽丘墟。临别赠言,幸承恩于伟饯;登高作赋,是所望于群公。敢竭鄙怀,恭疏短引;一言均赋,四韵俱成。请洒潘江,各倾陆海云尔:
滕王高阁临江渚,佩玉鸣鸾罢歌舞。
画栋朝飞南浦云,珠帘暮卷西山雨。
闲云潭影日悠悠,物换星移几度秋。
阁中帝子今何在?槛外长江空自流。\r
\r
""";

public static void main(String[] args) throws Exception {
for (int i = 0; i < 600; i++) {
new Thread(() -> {
try {
httpPostMultipartFormData(new File("/data/abc.png"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}).start();
}
System.out.println("线程启动成功");
}

private static void httpPostMultipartFormData(File file) throws Exception {
String host = "172.20.103.88";
String path = "/fuck/file2";
int port = 8080;
try (Socket client = newSocket(host, port);
OutputStream os = client.getOutputStream();
InputStream is = client.getInputStream()) {

// 分隔符,分隔符前面必须加上--,否则不会被识别
String boundary = UUID.randomUUID().toString().replace("-", "");

byte[] data = writeFile(file, boundary);

writeLine(os, "POST " + path + " HTTP/1.1");
writeLine(os, "host:" + host + ":8080");
writeLine(os, "Connection:keep-alive");
writeLine(os, "Content-Type:multipart/form-data;boundary=" + boundary);
writeLine(os, "Content-Length:" + data.length);
writeLine(os, null);

System.out.println("开始上传");
// int c = 0;
for (int i = 0; i < data.length; i++) {
if (i != 0 && i % 100 == 0) {
Thread.sleep(8000L);
// c++;
// if (c == 3) {
// client.close();
// }
System.out.println("睡一会儿..." + i);
os.flush();
}
os.write(data[i]);
}
os.flush();

printResponse(is);
}
}

private static byte[] writeFile(File file, String boundary) throws IOException {
String mimeType = Files.probeContentType(file.toPath());

ByteArrayOutputStream os = new ByteArrayOutputStream();
writeLine(os, "--" + boundary);
writeLine(os, "Content-Disposition: form-data; name=\"file\"; filename=\"abc.png\"");
writeLine(os, "Content-Type: " + mimeType);
writeLine(os, null);

try (InputStream is = new FileInputStream(file)) {
byte[] bytes = new byte[1024];
int len;
while ((len = is.read(bytes)) != -1) {
os.write(bytes, 0, len);
}
}
writeLine(os, null);
writeLine(os, "--" + boundary + "--");
return os.toByteArray();
}

// private static class MultipartFile {
// private String header;
// private String contentType;
// private File file;
// private String boundary;
//
// public MultipartFile(File file) {
// this.file = file;
// this.header = buildHeader();
// }
//
// private String buildHeader() {
// this.header = new StringBuilder()
// .append("--" + boundary).append(SPLIT)
// .append("Content-Disposition: form-data; name=\"file\"; filename=\"" + file.getName() + "\"").append(SPLIT)
// .toString();
// }
// }

// private static void httpPostChunked(byte[] data, int segment) throws IOException {
// try (Socket client = newSocket("misc.test.com", 443);
// OutputStream os = client.getOutputStream();
// InputStream is = client.getInputStream()) {
//
// writeLine(os, "POST /dump.htm HTTP/1.1");
// writeLine(os, "host:misc.test.com");
// writeLine(os, "Connection:close");
// writeLine(os, "Transfer-Encoding: chunked");
// writeLine(os, null);
//
// int len = data.length / segment;
// for (int i = 0; i < segment; i++) {
// int size = len;
// if (i == segment - 1) {
// size = data.length - (i * len);
// }
// writeLine(os, Integer.toString(size, 16));
// os.write(data, len * i, size);
// writeLine(os, null);
// }
// writeLine(os, "0");
// writeLine(os, null);
// printResponse(is);
// }
// }

private static void writeLine(OutputStream os, String str) throws IOException {
byte[] data;
if (str != null) {
data = (str + SPLIT).getBytes(StandardCharsets.UTF_8);
} else {
data = SPLIT.getBytes(StandardCharsets.UTF_8);
}
os.write(data);
}

private static void httpPost(byte[] body) throws Exception {
String host = "nginx-test.test.cn";
// String path = "/test/file.htm";
// String path = "/fuck/file2";
String path = "/api/test/sleep.htm?ms=100";
int port = 8080;

try (Socket client = newSocket(host, port);
OutputStream os = client.getOutputStream();
InputStream is = client.getInputStream()) {
writeLine(os, "POST " + path + " HTTP/1.1");
writeLine(os, "host:" + host);
writeLine(os, "Connection:close");
writeLine(os, "Content-Type:text/plain;charset=utf-8");
writeLine(os, "Content-Length:" + body.length);
writeLine(os, null);
os.flush();

System.out.println("开始上传");
for (int i = 0; i < body.length; i++) {
if (i != 0 && i % 100 == 0) {
Thread.sleep(300L);
System.out.println("睡一会儿..." + i);
os.flush();
}
os.write(body[i]);
}
os.flush();
// os.write(body);

printResponse(is);
}
}


private static Socket newSocket(String host, int port) {
try {
if (port == 443) {
return SSLSocketFactory.getDefault().createSocket(host, port);
} else {
return new Socket(host, port);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}

private static void printResponse(InputStream is) throws IOException {
System.out.println("\n-----输出内容-----\n");
int length;
byte[] buffer = new byte[1024];
while ((length = is.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, length));
}
}
}

Js 计算文件MD5的两种方式

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
// 方式1:使用crypto-js
import CryptoJSMD5 from 'crypto-js/md5'
import CryptoJSEncLatin1 from 'crypto-js/enc-latin1'
import CryptoJSEncHex from 'crypto-js/enc-hex'
getFileMD5(file) {
const start = new Date().getTime()
return new Promise((resolve) => {
const fileReader = new FileReader()
fileReader.onloadend = (ev) => {
const md5 = CryptoJSMD5(
CryptoJSEncLatin1.parse(ev.target.result)
).toString(CryptoJSEncHex)
console.log(
`计算MD5, file=${file.name}, size=${file.size}, md5=${md5}, elapsed=${
new Date().getTime() - start
}ms`
)
resolve(md5)
}
fileReader.readAsBinaryString(file)
})
},

// 方式2:使用spark-md5
import SparkMD5 from 'spark-md5'
getFileMD5(file) {
const start = new Date().getTime()
return new Promise((resolve) => {
const fileReader = new FileReader()
fileReader.onloadend = (ev) => {
const md5 = SparkMD5.hashBinary(ev.target.result)
console.log(
`计算MD5, file=${file.name}, size=${file.size}, md5=${md5}, elapsed=${
new Date().getTime() - start
}ms`
)
resolve(md5)
}
fileReader.readAsBinaryString(file)
})
},

实测使用spark-md5计算要快一些

使用div画table

网上找到的两种方法,先记录下备用。

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
<!DOCTYPE html>
<html>
<head>
<style>
.table-tr {
width: 1200px;
}
.table-td,.table-tdRowEnd,.table-tdLastRow,.table-tdLastRowEnd{
float: left;
width: 198px;
height: 30px;
line-height: 30px;
border-style: solid;
}
.table-td {
border-width: 1px 0px 0px 1px;
}
.table-tdRowEnd {
border-width: 1px 1px 0px 1px;
}
.table-tdLastRow {
border-width: 1px 0px 1px 1px;
}
.table-tdLastRowEnd {
border-width: 1px 1px 1px 1px;
}
</style>
</head>
<body>
<div class="table-tr" >
<div class="table-td">姓名</div>
<div class="table-td">年龄</div>
<div class="table-td">电话</div>
<div class="table-td">QQ号</div>
<div class="table-td">邮箱地址</div>
<div class="table-tdRowEnd">主页</div>
</div>

<div class="table-tr">
<div class="table-td">小明</div>
<div class="table-td">18</div>
<div class="table-td">123456789</div>
<div class="table-td">123456</div>
<div class="table-td">admin@admin.com</div>
<div class="table-tdRowEnd">feiniaomy.com</div>
</div>
<div class="table-tr" >
<div class="table-td">小红</div>
<div class="table-td">20</div>
<div class="table-td">123456789</div>
<div class="table-td">654321</div>
<div class="table-td">xxxx@xx.com</div>
<div class="table-tdRowEnd">baidu.com</div>
</div>
<div class="table-tr" >
<div class="table-tdLastRow">小蓝</div>
<div class="table-tdLastRow">20</div>
<div class="table-tdLastRow">null</div>
<div class="table-tdLastRow">null</div>
<div class="table-tdLastRow"></div>
<div class="table-tdLastRowEnd"></div>
</div>
</body>
</html>
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
<!DOCTYPE html>
<html>
<head>
<style>
ul{margin:0;padding:0;list-style:none;}
.table{display:table;border-collapse:collapse;border:1px solid #ccc;}
.table-caption{display:table-caption;margin:0;padding:0;font-size:16px;}
.table-column-group{display:table-column-group;}
.table-column{display:table-column;width:200px;}
.table-row-group{display:table-row-group;}
.table-row{display:table-row;}
.table-row-group .table-row:hover,.table-footer-group .table-row:hover{background:#f6f6f6;}
.table-cell{display:table-cell;padding:5px;border:1px solid #ccc;}
.table-header-group{display:table-header-group;background:#eee;font-weight:bold;}
.table-footer-group{display:table-footer-group;}
</style>
</head>
<body>
<div class="table">
<div class="table-column-group">
<div class="table-column"></div>
<div class="table-column"></div>
<div class="table-column"></div>
</div>
<div class="table-header-group">
<ul class="table-row">
<li class="table-cell">序号</li>
<li class="table-cell">姓名</li>
<li class="table-cell">年龄</li>
</ul>
</div>
<div class="table-row-group">
<ul class="table-row">
<li class="table-cell">1</li>
<li class="table-cell">小明</li>
<li class="table-cell">19</li>
</ul>
<ul class="table-row">
<li class="table-cell">2</li>
<li class="table-cell">小红</li>
<li class="table-cell">21</li>
</ul>
<ul class="table-row">
<li class="table-cell">3</li>
<li class="table-cell">小蓝</li>
<li class="table-cell">26</li>
</ul>
</div>
<div class="table-footer-group">
<ul class="table-row">
<li class="table-cell">底部</li>
<li class="table-cell">底部</li>
<li class="table-cell">底部</li>
</ul>
</div>
</div>
</body>
</html>

RSA 公钥和私钥生成

私钥生成

1
openssl genrsa -out rsa_private.pem 1024

注意:这里生成的是 PKCS1 格式的文件,也称之为传统的私钥格式。

生成公钥

1
openssl rsa -in rsa_private.pem -out rsa_public.pem -pubout

格式转换

把 RSA 私钥转 PKCS1 转换为 PKCS8 格式,执行如下:

1
openssl pkcs8 -topk8 -inform PEM -in rsa_private.pem -outform PEM -nocrypt -out rsa_private_pkcs8.pem

把 RSA 私钥 PKCS8 格式转换为 PKCS1 格式,执行如下:

1
openssl rsa -in rsa_private_pkcs8.pem -out pkcs1.pem

参考:https://veryitman.com/2019/05/11/macOS-%E7%94%9F%E6%88%90-RSA-%E5%85%AC%E9%92%A5%E5%92%8C%E7%A7%81%E9%92%A5/

Java生成GBK所有字符,以及判断字符是否是GBK编码内字符

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/**
* 参见文档: <a href="https://zh.wikipedia.org/wiki/%E6%B1%89%E5%AD%97%E5%86%85%E7%A0%81%E6%89%A9%E5%B1%95%E8%A7%84%E8%8C%83">汉字内码扩展规范</a>
* <p>
* GBK/1:GB2312非汉字符号: A1~A9, A1~FE
* GBK/2:GB2312汉字: B0~F7, A1~FE
* GBK/3:扩充汉字: 81~A0, 40~FE (7F除外)
* GBK/4:扩充汉字: AA~FE, 40~A0 (7F除外)
* GBK/5:扩充非汉字: A8~A9, 40~A0 (7F除外)
*
* @author Gao Youbo
* @since 2022-08-23 16:34
*/
public class GBK {
private static final Charset CHARSET = Charset.forName("GBK");

/**
* 获取所有GBK编码字符
*/
public static List<String> getGBK() {
List<String> words = new ArrayList<>();
byte[] bytes = new byte[2];

// GBK/1:GB2312非汉字符号
for (int b1 = 0xA1; b1 <= 0xA9; b1++) {
bytes[0] = (byte) b1;
for (int b2 = 0xA1; b2 <= 0xFE; b2++) {
bytes[1] = (byte) b2;
words.add(new String(bytes, CHARSET));
}
}

// GBK/2:GB2312汉字
for (int b1 = 0xB0; b1 <= 0xF7; b1++) {
bytes[0] = (byte) b1;
for (int b2 = 0xA1; b2 <= 0xFE; b2++) {
bytes[1] = (byte) b2;
words.add(new String(bytes, CHARSET));
}
}

// GBK/3:扩充汉字
for (int b1 = 0x81; b1 <= 0xA0; b1++) {
bytes[0] = (byte) b1;
for (int b2 = 0x40; b2 <= 0xFE; b2++) {
bytes[1] = (byte) b2;
if (b2 != 0x7F) {
words.add(new String(bytes, CHARSET));
}
}
}

// GBK/4:扩充汉字
for (int b1 = 0xAA; b1 <= 0xFE; b1++) {
bytes[0] = (byte) b1;
for (int b2 = 0x40; b2 <= 0xA0; b2++) {
bytes[1] = (byte) b2;
if (b2 != 0x7F) {
words.add(new String(bytes, CHARSET));
}
}
}

// GBK/5:扩充非汉字
for (int b1 = 0xA8; b1 <= 0xA9; b1++) {
bytes[0] = (byte) b1;
for (int b2 = 0x40; b2 <= 0xA0; b2++) {
bytes[1] = (byte) b2;
if (b2 != 0x7F) {
words.add(new String(bytes, CHARSET));
}
}
}

return words;
}

public static boolean isGBK(String str) {
boolean isGBK = false;
char[] chars = str.toCharArray();
for (char c : chars) {
byte[] bytes = String.valueOf(c).getBytes(CHARSET);
if (bytes.length == 2) { // GBK 编码为两个字节
int b1 = bytes[0] & 0xff;
int b2 = bytes[1] & 0xff;
if (b1 >= 0xA1 && b1 <= 0xA9 && b2 >= 0xA1 & b2 <= 0xFE) {
isGBK = true;
break;
}

if (b1 >= 0xB0 && b1 <= 0xF7 && b2 >= 0xA1 & b2 <= 0xFE) {
isGBK = true;
break;
}

if (b1 >= 0x81 && b1 <= 0xA0 && b2 >= 0x40 & b2 <= 0xFE && b2 != 0x7F) {
isGBK = true;
break;
}

if (b1 >= 0xAA && b1 <= 0xFE && b2 >= 0x40 & b2 <= 0xA0 && b2 != 0x7F) {
isGBK = true;
break;
}

if (b1 >= 0xA8 && b1 <= 0xA9 && b2 >= 0x40 & b2 <= 0xA0 && b2 != 0x7F) {
isGBK = true;
break;
}
}
}
return isGBK;
}
}

Java实现Base128编码

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
/**
* @author Gao Youbo
* @since 2022-08-19 10:28
*/
public class Base128 {
private static final char[] DEFAULT_CHAR_TABLE = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z', '-', '_',

'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ',
'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π',
'ρ', 'ς', 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ',
'ω', 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ',
'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î',
'ï', 'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö',
'÷', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ',
'ÿ', 'Ā', 'ā', 'Ă', 'ă', 'Ą', 'ą', 'Ć',
};

private final char[] charTable;
private final HashMap<Character, Integer> indexTable;

public Base128() {
this(DEFAULT_CHAR_TABLE);
}

public Base128(char[] charTable) {
this.charTable = charTable;
this.indexTable = new HashMap<>(charTable.length);
for (int j = 0; j < charTable.length; j++) {
indexTable.put(charTable[j], j);
}
}

private String encodeToString(byte[] data) {
if (data == null || data.length == 0) {
return "";
}
StringBuilder sb = new StringBuilder();
int tail = 0;
for (int i = 0; i < data.length; i++) {
int mov = (i % 7 + 1);
int curr = 0xFF & data[i];
int code = tail + (curr >> mov);
sb.append(charTable[code]);
tail = (0xFF & (curr << (8 - mov))) >> 1;
if (mov == 7) {
sb.append(charTable[tail]);
tail = 0;
}
}
sb.append(charTable[tail]);
return sb.toString();
}

private byte[] decode(String str) {
if (StringUtils.isBlank(str)) {
return new byte[]{};
}
int length = (int) Math.floor(str.length() * 0.875);
byte[] result = new byte[length];
int idx = 0;
int head = indexTable.get(str.charAt(0)) << 1;
for (int i = 1; i < str.length(); ) {
int mod = i % 8;
int code = indexTable.get(str.charAt(i++));
result[idx++] = (byte) (0xFF & (head + (code >> (7 - mod))));
if (mod == 7) {
head = 0xFF & (indexTable.get(str.charAt(i++)) << 1);
} else {
head = 0xFF & (code << (mod + 1));
}
}
return result;
}
}

下面提供一份更全的编码字节表,使用这个表可以实现Base256等

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
private static final char[] symbolTable = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z', '-', '_',

'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ',
'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π',
'ρ', 'ς', 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ',
'ω', 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ',
'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î',
'ï', 'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö',
'÷', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ',
'ÿ', 'Ā', 'ā', 'Ă', 'ă', 'Ą', 'ą', 'Ć',

'ć',
'Ĉ', 'ĉ', 'Ċ', 'ċ', 'Č', 'č', 'Ď', 'ď',
'Đ', 'đ', 'Ē', 'ē', 'Ĕ', 'ĕ', 'Ė', 'ė',
'Ę', 'ę', 'Ě', 'ě', 'Ĝ', 'ĝ', 'Ğ', 'ğ',
'Ġ', 'ġ', 'Ģ', 'ģ', 'Ĥ', 'ĥ', 'Ħ', 'ħ',
'Ĩ', 'ĩ', 'Ī', 'ī', 'Ĭ', 'ĭ', 'Į', 'į',
'İ', 'ı', 'IJ', 'ij', 'Ĵ', 'ĵ', 'Ķ', 'ķ',
'ĸ', 'Ĺ', 'ĺ', 'Ļ', 'ļ', 'Ľ', 'ľ', 'Ŀ',
'ŀ', 'Ł', 'ł', 'Ń', 'ń', 'Ņ', 'ņ', 'Ň',
'ň', 'ʼn', 'Ŋ', 'ŋ', 'Ō', 'ō', 'Ŏ', 'ŏ',
'Ő', 'ő', 'Œ', 'œ', 'Ŕ', 'ŕ', 'Ŗ', 'ŗ',
'Ř', 'ř', 'Ś', 'ś', 'Ŝ', 'ŝ', 'Ş', 'ş',
'Š', 'š', 'Ţ', 'ţ', 'Ť', 'ť', 'Ŧ', 'ŧ',
'Ũ', 'ũ', 'Ū', 'ū', 'Ŭ', 'ŭ', 'Ů', 'ů',
'Ű', 'ű', 'Ų', 'ų', 'Ŵ', 'ŵ', 'Ŷ', 'ŷ',
'Ÿ', 'Ź', 'ź', 'Ż', 'ż', 'Ž', 'ž', 'ſ',
'ƀ', 'Ɓ', 'Ƃ', 'ƃ', 'Ƅ', 'ƅ', 'Ɔ', 'Ƈ',
'ƈ', 'Ɖ', 'Ɗ', 'Ƌ', 'ƌ', 'ƍ', 'Ǝ', 'Ə',
'Ɛ', 'Ƒ', 'ƒ', 'Ɠ', 'Ɣ', 'ƕ', 'Ɩ', 'Ɨ',
'Ƙ', 'ƙ', 'ƚ', 'ƛ', 'Ɯ', 'Ɲ', 'ƞ', 'Ɵ',
'Ơ', 'ơ', 'Ƣ', 'ƣ', 'Ƥ', 'ƥ', 'Ʀ', 'Ƨ',
'ƨ',

// '=', '*', '&', '^', '%', '$', '#', '@'
};

获取GB2312编码所有汉字

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
private static final Charset CHARSET = Charset.forName("GB2312");

/**
* 获取GB2312所有汉字
* “高位字节”的范围是0xB0-0xF7,“低位字节”的范围是0xA0-0xFE
*/
private static List<String> getGB2312() {
List<String> words = new ArrayList<>();
byte[] bytes = new byte[2];
for (int b1 = 176; b1 < 248; b1++) {
bytes[0] = (byte) b1;
for (int b2 = 161; b2 < 255; b2++) {
bytes[1] = (byte) b2;
words.add(new String(bytes, CHARSET));
}
}
return words;
}

/**
* 对于gb2312来讲,首字节码位从0×81至0×FE,尾字节码位分别是0×40至0×FE
*/
public static boolean isGB2312(String str) {
boolean isGB2312 = false;
char[] chars = str.toCharArray();
for (char c : chars) {
byte[] bytes = ("" + c).getBytes(CHARSET);
if (bytes.length == 2) {
int[] ints = new int[2];
ints[0] = bytes[0] & 0xff;
ints[1] = bytes[1] & 0xff;
if (ints[0] >= 0x81 && ints[0] <= 0xFE && ints[1] >= 0x40 && ints[1] <= 0xFE) {
isGB2312 = true;
break;
}
}
}
return isGB2312;
}

Alfred 使用iTerm

ADD:我在网上找到了一篇更完整的文章,文章中详细介绍了,这么配合使用alfred + iTerm2 + ssh config快速连接远程服务器:https://juejin.cn/post/6844903909916426248

Alfred执行命令时默认使用的是MaxOS系统自带的Terminal,而我平时使用的都是iTerm。在Github上找到了解决方案:https://github.com/vitorgalvao/custom-alfred-iterm-scripts

设置方式直接截图说明:

WechatIMG62.png

配置内容如下(这个内容是我从上面那个Github项目中Copy出来的,可以自行去项目中Copy最新的):

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
-- For the latest version:
-- https://github.com/vitorgalvao/custom-alfred-iterm-scripts

-- Set this property to true to always open in a new window
property open_in_new_window : false

-- Set this property to false to reuse current tab
property open_in_new_tab : true

-- Handlers
on new_window()
tell application "iTerm" to create window with default profile
end new_window

on new_tab()
tell application "iTerm" to tell the first window to create tab with default profile
end new_tab

on call_forward()
tell application "iTerm" to activate
end call_forward

on is_running()
application "iTerm" is running
end is_running

on has_windows()
if not is_running() then return false

tell application "iTerm"
if windows is {} then return false
if tabs of current window is {} then return false
if sessions of current tab of current window is {} then return false

set session_text to contents of current session of current tab of current window
if words of session_text is {} then return false
end tell

true
end has_windows

on send_text(custom_text)
tell application "iTerm" to tell the first window to tell current session to write text custom_text
end send_text

-- Main
on alfred_script(query)
if has_windows() then
if open_in_new_window then
new_window()
else if open_in_new_tab then
new_tab()
else
-- Reuse current tab
end if
else
-- If iTerm is not running and we tell it to create a new window, we get two
-- One from opening the application, and the other from the command
if is_running() then
new_window()
else
call_forward()
end if
end if

-- Make sure a window exists before we continue, or the write may fail
repeat until has_windows()
delay 0.01
end repeat

send_text(query)
call_forward()
end alfred_script

这也备份一下修改之前的配置,防止丢失:

1
2
3
4
5
6
on alfred_script(q)
tell application "Terminal"
activate
do script q
end tell
end alfred_script

Ipv4地址段匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class IpMatcher {
public static boolean matchIPV4(String ip, String cidr) {
String[] ips = ip.split("\\.");
if (ips.length != 4) {
return false;
}
int ipAddr = (Integer.parseInt(ips[0]) << 24)
| (Integer.parseInt(ips[1]) << 16)
| (Integer.parseInt(ips[2]) << 8) | Integer.parseInt(ips[3]);
int type = Integer.parseInt(cidr.replaceAll(".*/", ""));
int mask = 0xFFFFFFFF << (32 - type);
String cidrIp = cidr.replaceAll("/.*", "");
String[] cidrIps = cidrIp.split("\\.");
int cidrIpAddr = (Integer.parseInt(cidrIps[0]) << 24)
| (Integer.parseInt(cidrIps[1]) << 16)
| (Integer.parseInt(cidrIps[2]) << 8)
| Integer.parseInt(cidrIps[3]);
return (ipAddr & mask) == (cidrIpAddr & mask);
}

public static void main(String[] args) {
System.out.println(matchIPV4("172.16.0.1", "172.16.0.0/20"));
}
}

使用certbot申请https证书

我的博客图片放在阿里云的oss,oss绑定域名的https证书过期很久了,之前一直懒得弄,周末抽时间弄一下,证书是使用certbot申请的,步骤如下:

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
➜  ~ sudo certbot certonly --manual --preferred-challenges=dns-01 --server=https://acme-v02.api.letsencrypt.org/directory

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Please enter the domain name(s) you would like on your certificate (comma and/or
space separated) (Enter 'c' to cancel): file.mspring.org
Requesting a certificate for file.mspring.org

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name:

_acme-challenge.file.mspring.org.

with the following value:

eyCcqOYGl7KWxr-mGpp7Y77j6iEV-rC0dXyq8-LnL3k

Before continuing, verify the TXT record has been deployed. Depending on the DNS
provider, this may take some time, from a few seconds to multiple minutes. You can
check if it has finished deploying with aid of online tools, such as the Google
Admin Toolbox: https://toolbox.googleapps.com/apps/dig/#TXT/_acme-challenge.file.mspring.org.
Look for one or more bolded line(s) below the line ';ANSWER'. It should show the
value(s) you've just added.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/file.mspring.org-0001/fullchain.pem
Key is saved at: /etc/letsencrypt/live/file.mspring.org-0001/privkey.pem
This certificate expires on 2022-06-24.
These files will be updated when the certificate renews.

NEXT STEPS:
- This certificate will not be renewed automatically. Autorenewal of --manual certificates requires the use of an authentication hook script (--manual-auth-hook) but one was not provided. To renew this certificate, repeat this same certbot command before the certificate's expiry date.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
* Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
* Donating to EFF: https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -