更新于 

CurlCmd

请求参数

对数字资产对象的监测通过Curl进行,其请求参数如下表所示:

Parameters Interpretation
-L 自动重定向,当前资源已经移至其他地址时,自动重定向到新的地址
-k 解除curl的安全认证(不解除有的服务就会报60错误)
–connect-timeout [seconds] 设置最大连接时间(后接时间)
-m/–max-time [seconds] 设置最大操作总时间(后接时间)
-w 按照后面的格式写出返回消息
time_namelookup DNS解析域名的时间
time_connect client和server端建立TCP连接的时间(包括解析域名的时间)
time_starttransfer 从开始到server 响应第一个字节的时间,即发送请求和服务器响应的时间之和(包括前面2个时间)
time_total 从开始到server发送完所有的响应数据,并关闭连接的时间(包括前面所有时间)
speed_download 下载速度,单位byte/s
http_code 返回的HTTP状态码
size_download 返回内容大小,单位byte
content_type 返回内容的格式
Request示例

curl -L -k –connect-timeout 30 -m 60 -w “\n”%{time_namelookup}::%{time_connect}::%{time_starttransfer}::%{time_total}::%{speed_download}::%{http_code}::%{size_download}::%{content_type}”\n” URL

返回说明

返回内容格式

Curl在不同环境下返回的格式不同:

  • Windows环境:Windows环境下返回的内容最后一行即为上述请求参数的结果,以“::”分开。
  • Linux环境:Linux环境下返回内容的格式和Windows相同,但需注意的是Linux下输入命令和输出结果的每一行都会自动加上双引号,所以代码中Linux环境下输入命令的url前后都不需加双引号,否则协议会被视为http而导致命令无法执行;返回结果中由于最后一行是一个双引号,所以要取倒数第二行,同时倒数第二行也会自动加上双引号,要进行处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//输入命令处理
if (this.osName.startsWith("Windows")) {
curlCmd = "curl -L -k --connect-timeout " + con_timeout + " -m " + ope_timeout + " -w \"\\n\"%{time_namelookup}::%{time_connect}::%{time_starttransfer}::%{time_total}::%{speed_download}::%{http_code}::%{size_download}::%{content_type}\"\\n\" \""
+ url + "\"";
} else if ((this.osName.contains("Linux")) || (this.osName.contains("CentOS"))) {
curlCmd = "curl -L -k --connect-timeout " + con_timeout + " -m " + ope_timeout + " -w \"\\n\"%{time_namelookup}::%{time_connect}::%{time_starttransfer}::%{time_total}::%{speed_download}::%{http_code}::%{size_download}::%{content_type}\"\\n\" "
+ url;
}

//返回结果处理
if (this.osName.startsWith("Windows")) {
mess = response.get(size1 - 1);
} else if ((this.osName.contains("Linux")) || (osName.contains("CentOS"))) {
String messT = response.get(size1 - 2);
mess = messT.substring(1, messT.length() - 1);
}

返回内容处理

由于使用curl工具时,命令行内显示的是inputstream,即命令执行的结果;同时命令还会产生错误流errorstream,显示每隔一段时间接受数据量、下载速度等内容,会不断生成。输出缓冲区的size有限,如果不及时处理这两个流,就会发生阻塞,失去响应,所以程序中另外开了一个线程去读取错误流,保证两个流都及时的被读取,防止发生阻塞。

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
Process process = Runtime.getRuntime().exec(curlCmd);
final InputStreamReader ir1 = new InputStreamReader(process.getInputStream(), "GBK");
final InputStreamReader ir2 = new InputStreamReader(process.getErrorStream(), "GBK");
BufferedReader br1 = new BufferedReader(ir1);
String line = null;
final List<String> response = new ArrayList<String>();
final List<String> errs = new ArrayList<String>();
//这里,如果无法正常curl,就会不断产生errorStream,因为输出缓冲区的size有限,如果不及时处理的话就会卡住,所以这里要同时处理errorStream;
//但是如果能正常curl的话,如果不及时处理inputStream也会卡住;要及时读取那个不断生成的流
Thread thread = new Thread(() -> {
BufferedReader br2 = new BufferedReader(ir2);
try {
String line2 = null;
while ((line2 = br2.readLine()) != null) {
if (!"".equals(line2)) {
errs.add(line2);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
ir2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
thread.start();
while ((line = br1.readLine()) != null) {
if (!"".equals(line)) {
response.add(line);
}
}
thread.join();
int exitValue = process.waitFor();
process.destroy();
ir1.close();

返回内容解析

返回内容提取最后一行或倒数第二行,按照“::”进行分割,一般情况下得到8个字符串,但是当curl失败时,最后一个参数“content_type”的返回结果为空,则得到7个字符串,需要单独处理。

返回内容经分割后,分别对应请求中各参数的结果,其中分为2大类结果,分别是Curl成功Curl不成功,对应命令的exitvalue为0和不为0。

具体分类见下表:

cURL默认连接超时时间为20秒,即–connect-timeout参数设置为20秒以上时,仍然会以20秒作为最大连接时间。

当达到默认最大连接时间(即20秒)仍未连接上时会报7错误;当达到用户设定最大连接时间(只有设置小于20秒才有效)仍未连接上时会报28错误;当总操作时间达到用户设定最大操作时间(即-m参数)仍未完成时报28错误

7错误是达到默认连接超时时间的错误,28错误是达到用户设置超时时间的错误。

返回结果存储

Database Field Convertion Interpretation
statusCode Original + Self-Define 按照上表方式处理
connect_time time_connect - time_namelookup 截止到建立完TCP连接的时间减去截止到解析完域名的时间
server_time time_starttransfer - time_connect 截止到Server响应第一个字节的时间减去截止到建立完TCP连接的时间
transfer_time time_total - time_starttransfer 总时间减去截止到server响应第一个字节的时间
total_time time_total 原内容
download_speed speed_download 原内容
data_byte size_download 原内容
content_type content_type 原内容

计算公式为:

total_time = namelookup_time + connect_time + server_time + transfer_time

整个请求过程可以描述为:

域名解析 → 建立TCP连接(三次握手) → 发送http的Get请求(请求能力文档或图层) → 服务器处理 → 传输数据(能力文档或图层)

其中下载速度是指整个过程的平均下载速度,即download_speed = data_byte/total_time

server_time实际上包含了发送Get请求的时间和服务器响应时间两个时间,无法分开。

需要注意的是,curl如果失败的话,返回的各种时间就会错乱,只有总时间始终有效,所以在数据库存储过程中,curl失败的情况只有total_time始终有效,其他时间可能记录为0。

详细代码

CurlCmd.java
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
package com.rsgis.utils;

import com.rsgis.bean.CurlResponse;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

@NoArgsConstructor
@AllArgsConstructor
public class CurlCmd {
private String url;
private int con_timeout;
private int ope_timeout;
private String osName;

public CurlResponse doCurlCmd() throws InterruptedException {
try {
String curlCmd = null;
//linux中在处理和输出字符串时会自动加双引号!!!
if (this.osName.startsWith("Windows")) {
curlCmd = "curl -L -k --connect-timeout " + con_timeout + " -m " + ope_timeout + " -w \"\\n\"%{time_namelookup}::%{time_connect}::%{time_starttransfer}::%{time_total}::%{speed_download}::%{http_code}::%{size_download}::%{content_type}\"\\n\" \""
+ url + "\"";
} else if ((this.osName.contains("Linux")) || (this.osName.contains("CentOS"))) {
curlCmd = "curl -L -k --connect-timeout " + con_timeout + " -m " + ope_timeout + " -w \"\\n\"%{time_namelookup}::%{time_connect}::%{time_starttransfer}::%{time_total}::%{speed_download}::%{http_code}::%{size_download}::%{content_type}\"\\n\" "
+ url;
}

Process process = Runtime.getRuntime().exec(curlCmd);
final InputStreamReader ir1 = new InputStreamReader(process
.getInputStream(), "GBK");
final InputStreamReader ir2 = new InputStreamReader(process
.getErrorStream(), "GBK");
BufferedReader br1 = new BufferedReader(ir1);

String line = null;
final List<String> response = new ArrayList<String>();
final List<String> errs = new ArrayList<String>();

//这里,如果无法正常curl,就会不断产生errorStream,因为输出缓冲区的size有限,如果不及时处理的话就会卡住,所以这里要同时处理errorStream;
//但是如果能正常curl的话,如果不及时处理inputStream也会卡住;要及时读取那个不断生成的流
Thread thread = new Thread(() -> {
BufferedReader br2 = new BufferedReader(ir2);
try {
String line2 = null;
while ((line2 = br2.readLine()) != null) {
if (!"".equals(line2)) {
errs.add(line2);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
ir2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
thread.start();

while ((line = br1.readLine()) != null) {
if (!"".equals(line)) {
response.add(line);
}
}

thread.join();
int exitValue = process.waitFor();
process.destroy();

ir1.close();
return parseMessage(response, exitValue);

} catch (IOException e) {
System.out.println("IOException " + e.getMessage());
return null;
}
}

private CurlResponse parseMessage(List<String> response, int exitValue) {
int size1 = response.size();
CurlResponse cr = new CurlResponse();
if (size1 == 0) {
cr.setTime_connect(0);
cr.setTime_server(0);
cr.setTime_transfer(0);
cr.setTime_total(0);
cr.setData_size(0);
cr.setDownload_speed(0);
cr.setStatusCode(700);
cr.setContentType("");
return cr;
} else {
String mess = null;
if (this.osName.startsWith("Windows")) {
mess = response.get(size1 - 1);
} else if ((this.osName.contains("Linux")) || (osName.contains("CentOS"))) {
String messT = response.get(size1 - 2);
mess = messT.substring(1, messT.length() - 1);
}
assert mess != null;
String[] message = mess.split("::");
String type = null;
if (message.length == 8) {
type = message[7].toLowerCase();
} else {
type = "";
}
boolean valid = false;
if (exitValue == 0) {
valid = ContentValid(response,type);//判断返回内容是否有效
}

float time0 = Float.parseFloat(message[0]);
float time1 = Float.parseFloat(message[1]);
float time2 = Float.parseFloat(message[2]);
float time3 = Float.parseFloat(message[3]);
int code = Integer.parseInt(message[5]);
String data_type = type;

float connect, server, tran, total, speed;
float result_speed, result_connect, result_server, result_tran, result_total;
if (exitValue == 0) {
connect = time1 - time0;
server = time2 - time1;
tran = time3 - time2;
} else {
connect = 0;
server = 0;
tran = 0;
}

total = time3;
speed = Float.parseFloat(message[4]);

BigDecimal bg1 = new BigDecimal(speed);
BigDecimal bg2 = new BigDecimal(connect);
BigDecimal bg3 = new BigDecimal(server);
BigDecimal bg4 = new BigDecimal(tran);
BigDecimal bg5 = new BigDecimal(total);

result_speed = bg1.setScale(3, RoundingMode.HALF_UP).floatValue();
result_connect = bg2.setScale(3, RoundingMode.HALF_UP).floatValue();
result_server = bg3.setScale(3, RoundingMode.HALF_UP).floatValue();
result_tran = bg4.setScale(3, RoundingMode.HALF_UP).floatValue();
result_total = bg5.setScale(3, RoundingMode.HALF_UP).floatValue();

cr.setContentType(data_type);
cr.setTime_connect(result_connect);
cr.setTime_transfer(result_tran);
cr.setTime_server(result_server);
cr.setTime_total(result_total);
cr.setData_size(Integer.parseInt(message[6]));
cr.setDownload_speed(result_speed);

if (exitValue != 0)//IOException, curl failed
{
cr.setStatusCode(exitValue);
} else {//curl succes
if ((code == 200) && (!valid))//invalid services or layers
{
cr.setStatusCode(600);;//self-defined statusCode
} else//valid services or layers
{
cr.setStatusCode(code);
}
}
return cr;
}
}
}