未验证 提交 bf6491f4 编写于 作者: K kezhenxu94 提交者: GitHub

[Feature] Provide Java Agent for Apache HttpClient 3.x (#4111)

* Add Java Agent for Apache HttpClient 3.x

* Update Supported-list.md

* Update pom.xml
上级 9dcd3c74
......@@ -264,6 +264,8 @@ jobs:
run: bash test/plugin/run.sh spring-4.1.x-scenario
- name: Run solrj 7.x (12)
run: bash test/plugin/run.sh solrj-7.x-scenario
- name: Run httpclient 2.0-3.1 (5)
run: bash test/plugin/run.sh httpclient-3.x-scenario
- name: Run httpclient 4.3.x-4.5.x (14)
run: bash test/plugin/run.sh httpclient-4.3.x-scenario
- name: Run httpasyncclient 4.0-4.1.3 (7)
......
<?xml version="1.0"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You under the Apache License, Version 2.0
~ (the "License"); you may not use this file except in compliance with
~ the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-sdk-plugin</artifactId>
<version>7.0.0-SNAPSHOT</version>
</parent>
<artifactId>apm-httpclient-3.x-plugin</artifactId>
<description>SkyWalking Java Agent for commons-httpclient (http://hc.apache.org/httpclient-3.x/)</description>
<packaging>jar</packaging>
<name>httpclient-3.x-plugin</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<commons-httpclient.version>3.1</commons-httpclient.version>
</properties>
<dependencies>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>${commons-httpclient.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.skywalking.apm.plugin.httpclient.v3;
import java.lang.reflect.Method;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.skywalking.apm.agent.core.context.CarrierItem;
import org.apache.skywalking.apm.agent.core.context.ContextCarrier;
import org.apache.skywalking.apm.agent.core.context.ContextManager;
import org.apache.skywalking.apm.agent.core.context.tag.Tags;
import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
/**
* @author kezhenxu94
*/
public class HttpClientExecuteInterceptor implements InstanceMethodsAroundInterceptor {
@Override
public void beforeMethod(
final EnhancedInstance objInst,
final Method method,
final Object[] allArguments,
final Class<?>[] argumentsTypes,
final MethodInterceptResult result) throws Throwable {
final HttpClient client = (HttpClient) objInst;
HostConfiguration hostConfiguration = (HostConfiguration) allArguments[0];
if (hostConfiguration == null) {
hostConfiguration = client.getHostConfiguration();
}
final HttpMethod httpMethod = (HttpMethod) allArguments[1];
final String remotePeer = httpMethod.getURI().getHost() + ":" + httpMethod.getURI().getPort();
final URI uri = httpMethod.getURI();
final String requestURI = getRequestURI(uri);
final ContextCarrier contextCarrier = new ContextCarrier();
final AbstractSpan span = ContextManager.createExitSpan(requestURI, contextCarrier, remotePeer);
span.setComponent(ComponentsDefine.HTTPCLIENT);
Tags.URL.set(span, uri.toString());
Tags.HTTP.METHOD.set(span, httpMethod.getName());
SpanLayer.asHttp(span);
for (CarrierItem next = contextCarrier.items(); next.hasNext(); ) {
next = next.next();
httpMethod.addRequestHeader(next.getHeadKey(), next.getHeadValue());
}
}
@Override
public Object afterMethod(
final EnhancedInstance objInst,
final Method method,
final Object[] allArguments,
final Class<?>[] argumentsTypes,
final Object ret) {
if (ret != null) {
final int statusCode = (Integer) ret;
final AbstractSpan span = ContextManager.activeSpan();
if (statusCode >= 400) {
span.errorOccurred();
Tags.STATUS_CODE.set(span, Integer.toString(statusCode));
}
}
ContextManager.stopSpan();
return ret;
}
@Override
public void handleMethodException(
final EnhancedInstance objInst,
final Method method,
final Object[] allArguments,
final Class<?>[] argumentsTypes,
final Throwable t) {
ContextManager.activeSpan().errorOccurred().log(t);
}
private String getRequestURI(URI uri) throws URIException {
String requestPath = uri.getPath();
return requestPath != null && requestPath.length() > 0 ? requestPath : "/";
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.skywalking.apm.plugin.httpclient.v3.define;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName;
/**
* @author kezhenxu94
*/
public class HttpClientInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
private static final String ENHANCE_CLASS = "org.apache.commons.httpclient.HttpClient";
private static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.httpclient.v3.HttpClientExecuteInterceptor";
@Override
protected ClassMatch enhanceClass() {
return byName(ENHANCE_CLASS);
}
@Override
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
return null;
}
@Override
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
return new InstanceMethodsInterceptPoint[]{
new InstanceMethodsInterceptPoint() {
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
return named("executeMethod")
.and(takesArguments(3))
.and(takesArgument(0, named("org.apache.commons.httpclient.HostConfiguration")))
.and(takesArgument(1, named("org.apache.commons.httpclient.HttpMethod")))
.and(takesArgument(2, named("org.apache.commons.httpclient.HttpState")));
}
@Override
public String getMethodsInterceptor() {
return INTERCEPT_CLASS;
}
@Override
public boolean isOverrideArgs() {
return false;
}
}
};
}
}
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
httpclient-3.x=org.apache.skywalking.apm.plugin.httpclient.v3.define.HttpClientInstrumentation
......@@ -81,6 +81,7 @@
<module>pulsar-plugin</module>
<module>netty-socketio-plugin</module>
<module>armeria-0.84.x-plugin</module>
<module>httpclient-3.x-plugin</module>
</modules>
<packaging>pom</packaging>
......
......@@ -19,7 +19,7 @@
* [Feign](https://github.com/OpenFeign/feign) 9.x
* [Netflix Spring Cloud Feign](https://github.com/spring-cloud/spring-cloud-netflix/tree/master/spring-cloud-starter-feign) 1.1.x, 1.2.x, 1.3.x
* [Okhttp](https://github.com/square/okhttp) 3.x
* [Apache httpcomponent HttpClient](http://hc.apache.org/) 4.2, 4.3
* [Apache httpcomponent HttpClient](http://hc.apache.org/) 2.0 -> 3.1, 4.2, 4.3
* [Spring RestTemplete](https://github.com/spring-projects/spring-framework) 4.x
* [Jetty Client](http://www.eclipse.org/jetty/) 9
* [Apache httpcomponent AsyncClient](https://hc.apache.org/httpcomponents-asyncclient-dev/) 4.x
......
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
registryItems:
applications:
- {httpclient-3.x-scenario: 2}
instances:
- {httpclient-3.x-scenario: 1}
operationNames:
- httpclient-3.x-scenario: [/httpclient-3.x-scenario/case/httpclient,
/httpclient-3.x-scenario/case/context-propagate]
heartbeat: []
segmentItems:
- applicationCode: httpclient-3.x-scenario
segmentSize: ge 3
segments:
- segmentId: not null
spans:
- operationName: /httpclient-3.x-scenario/case/context-propagate
operationId: 0
parentSpanId: -1
spanId: 0
spanLayer: Http
startTime: nq 0
endTime: nq 0
componentId: 1
componentName: ''
isError: false
spanType: Entry
peer: ''
peerId: 0
tags:
- {key: url, value: 'http://localhost:8080/httpclient-3.x-scenario/case/context-propagate'}
- {key: http.method, value: GET}
refs:
- {parentEndpointId: 0, parentEndpoint: /httpclient-3.x-scenario/case/httpclient,
networkAddressId: 0, entryEndpointId: 0, refType: CrossProcess, parentSpanId: 1,
parentTraceSegmentId: not null, parentServiceInstanceId: 1,
networkAddress: 'localhost:8080', entryEndpoint: /httpclient-3.x-scenario/case/httpclient,
entryServiceInstanceId: 1}
- segmentId: not null
spans:
- operationName: /httpclient-3.x-scenario/case/context-propagate
operationId: 0
parentSpanId: 0
spanId: 1
spanLayer: Http
startTime: nq 0
endTime: nq 0
componentId: 2
componentName: ''
isError: false
spanType: Exit
peer: localhost:8080
peerId: 0
tags:
- {key: url, value: 'http://localhost:8080/httpclient-3.x-scenario/case/context-propagate'}
- {key: http.method, value: GET}
- operationName: /httpclient-3.x-scenario/case/httpclient
operationId: 0
parentSpanId: -1
spanId: 0
spanLayer: Http
startTime: nq 0
endTime: nq 0
componentId: 1
componentName: ''
isError: false
spanType: Entry
peer: ''
peerId: 0
tags:
- {key: url, value: 'http://localhost:8080/httpclient-3.x-scenario/case/httpclient'}
- {key: http.method, value: GET}
\ No newline at end of file
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
type: tomcat
entryService: http://localhost:8080/httpclient-3.x-scenario/case/httpclient
healthCheck: http://localhost:8080/httpclient-3.x-scenario/healthCheck
framework: httpclient
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You under the Apache License, Version 2.0
~ (the "License"); you may not use this file except in compliance with
~ the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.skywalking</groupId>
<artifactId>httpclient-3.x-scenario</artifactId>
<version>5.0.0</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<compiler.version>1.8</compiler.version>
<test.framework>httpclient</test.framework>
<test.framework.version>3.1</test.framework.version>
</properties>
<name>skywalking-httpclient-3.x-scenario</name>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>${test.framework.version}</version>
</dependency>
</dependencies>
<build>
<finalName>httpclient-3.x-scenario</finalName>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${compiler.version}</source>
<target>${compiler.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.skywalking.apm.testcase.httpclient;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.methods.GetMethod;
/**
* @author kezhenxu94
*/
public class CaseServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
MultiThreadedHttpConnectionManager httpConnectionManager = new MultiThreadedHttpConnectionManager();
HttpClient client = new HttpClient(httpConnectionManager);
HttpMethod httpGet = new GetMethod("http://localhost:8080" + req.getContextPath() + "/case/context-propagate");
int statusCode = client.executeMethod(httpGet);
try (PrintWriter printWriter = resp.getWriter()) {
printWriter.write("success: " + statusCode);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
doGet(req, resp);
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.skywalking.apm.testcase.httpclient;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author kezhenxu94
*/
public class HealthCheckServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
try (PrintWriter writer = resp.getWriter()) {
writer.write("Success");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
doGet(req, resp);
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.skywalking.apm.testcase.httpclient;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author kezhenxu94
*/
public class ServletForContextPropagate extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("application/json");
try (PrintWriter out = resp.getWriter()) {
out.print("{'test':'test'}");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
doGet(req, resp);
}
}
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You under the Apache License, Version 2.0
~ (the "License"); you may not use this file except in compliance with
~ the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~
-->
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>skywalking-httpclient-scenario</display-name>
<servlet>
<servlet-name>caseServlet</servlet-name>
<servlet-class>org.apache.skywalking.apm.testcase.httpclient.CaseServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>healthCheck</servlet-name>
<servlet-class>org.apache.skywalking.apm.testcase.httpclient.HealthCheckServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>healthCheck</servlet-name>
<url-pattern>/healthCheck</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>caseServlet</servlet-name>
<url-pattern>/case/httpclient</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>servletForContextPropagate</servlet-name>
<servlet-class>org.apache.skywalking.apm.testcase.httpclient.ServletForContextPropagate</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servletForContextPropagate</servlet-name>
<url-pattern>/case/context-propagate</url-pattern>
</servlet-mapping>
</web-app>
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
2.0
2.0.2
3.0
3.0.1
3.1
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册