chore(project): 添加项目配置文件和忽略规则
- 添加 Babel 配置文件支持 ES6+ 语法转换 - 添加 ESLint 忽略规则和配置文件 - 添加 Git 忽略规则文件 - 添加 Travis CI 配置文件 - 添加 1.4.2 版本变更日志文件 - 添加 Helm 图表辅助模板文件 - 添加 Helm 忽略规则文件
This commit is contained in:
43
integration/dubbo-alibaba/pom.xml
Normal file
43
integration/dubbo-alibaba/pom.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 1999-2019 Seata.io Group.
|
||||
~
|
||||
~ Licensed 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">
|
||||
<parent>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-parent</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>seata-dubbo-alibaba</artifactId>
|
||||
<name>seata-dubbo-alibaba ${project.version}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-tm</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.dubbo.alibaba;
|
||||
|
||||
import com.alibaba.dubbo.common.extension.Activate;
|
||||
import com.alibaba.dubbo.rpc.Filter;
|
||||
import com.alibaba.dubbo.rpc.Invocation;
|
||||
import com.alibaba.dubbo.rpc.Invoker;
|
||||
import com.alibaba.dubbo.rpc.Result;
|
||||
import com.alibaba.dubbo.rpc.RpcContext;
|
||||
import com.alibaba.dubbo.rpc.RpcException;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.core.context.RootContext;
|
||||
import io.seata.core.constants.DubboConstants;
|
||||
import io.seata.core.model.BranchType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The type Transaction propagation filter.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
@Activate(group = {DubboConstants.PROVIDER, DubboConstants.CONSUMER}, order = 100)
|
||||
public class AlibabaDubboTransactionPropagationFilter implements Filter {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AlibabaDubboTransactionPropagationFilter.class);
|
||||
|
||||
@Override
|
||||
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
|
||||
if (!DubboConstants.ALIBABADUBBO) {
|
||||
return invoker.invoke(invocation);
|
||||
}
|
||||
String xid = RootContext.getXID();
|
||||
BranchType branchType = RootContext.getBranchType();
|
||||
|
||||
String rpcXid = getRpcXid();
|
||||
String rpcBranchType = RpcContext.getContext().getAttachment(RootContext.KEY_BRANCH_TYPE);
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("xid in RootContext[{}] xid in RpcContext[{}]", xid, rpcXid);
|
||||
}
|
||||
boolean bind = false;
|
||||
if (xid != null) {
|
||||
RpcContext.getContext().setAttachment(RootContext.KEY_XID, xid);
|
||||
RpcContext.getContext().setAttachment(RootContext.KEY_BRANCH_TYPE, branchType.name());
|
||||
} else {
|
||||
if (rpcXid != null) {
|
||||
RootContext.bind(rpcXid);
|
||||
if (StringUtils.equals(BranchType.TCC.name(), rpcBranchType)) {
|
||||
RootContext.bindBranchType(BranchType.TCC);
|
||||
}
|
||||
bind = true;
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("bind xid [{}] branchType [{}] to RootContext", rpcXid, rpcBranchType);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
return invoker.invoke(invocation);
|
||||
} finally {
|
||||
if (bind) {
|
||||
BranchType previousBranchType = RootContext.getBranchType();
|
||||
String unbindXid = RootContext.unbind();
|
||||
if (BranchType.TCC == previousBranchType) {
|
||||
RootContext.unbindBranchType();
|
||||
}
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("unbind xid [{}] branchType [{}] from RootContext", unbindXid, previousBranchType);
|
||||
}
|
||||
if (!rpcXid.equalsIgnoreCase(unbindXid)) {
|
||||
LOGGER.warn("xid in change during RPC from {} to {},branchType from {} to {}", rpcXid, unbindXid,
|
||||
rpcBranchType != null ? rpcBranchType : "AT", previousBranchType);
|
||||
if (unbindXid != null) {
|
||||
RootContext.bind(unbindXid);
|
||||
LOGGER.warn("bind xid [{}] back to RootContext", unbindXid);
|
||||
if (BranchType.TCC == previousBranchType) {
|
||||
RootContext.bindBranchType(BranchType.TCC);
|
||||
LOGGER.warn("bind branchType [{}] back to RootContext", previousBranchType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get rpc xid
|
||||
* @return
|
||||
*/
|
||||
private String getRpcXid() {
|
||||
String rpcXid = RpcContext.getContext().getAttachment(RootContext.KEY_XID);
|
||||
if (rpcXid == null) {
|
||||
rpcXid = RpcContext.getContext().getAttachment(RootContext.KEY_XID.toLowerCase());
|
||||
}
|
||||
return rpcXid;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
io.seata.integration.dubbo.alibaba.AlibabaDubboTransactionPropagationFilter
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.dubbo.alibaba;
|
||||
|
||||
import com.alibaba.dubbo.rpc.Invocation;
|
||||
import com.alibaba.dubbo.rpc.Invoker;
|
||||
import com.alibaba.dubbo.rpc.RpcContext;
|
||||
import io.seata.core.context.RootContext;
|
||||
import io.seata.core.model.BranchType;
|
||||
import io.seata.integration.dubbo.alibaba.mock.MockInvoker;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author wang.liang
|
||||
*/
|
||||
public class AlibabaDubboTransactionPropagationFilterTest {
|
||||
|
||||
private static final String DEFAULT_XID = "1234567890";
|
||||
|
||||
@Test
|
||||
public void testInvoke_And_RootContext() {
|
||||
AlibabaDubboTransactionPropagationFilter filter = new AlibabaDubboTransactionPropagationFilter();
|
||||
|
||||
// SAGA
|
||||
RpcContext.getContext().setAttachment(RootContext.KEY_XID, DEFAULT_XID);
|
||||
RpcContext.getContext().setAttachment(RootContext.KEY_BRANCH_TYPE, BranchType.SAGA.name());
|
||||
filter.invoke(new MockInvoker(() -> {
|
||||
assertThat(RootContext.getXID()).isEqualTo(DEFAULT_XID);
|
||||
assertThat(RootContext.getBranchType()).isEqualTo(BranchType.AT);
|
||||
}), null);
|
||||
assertThat(RootContext.unbind()).isNull();
|
||||
assertThat(RootContext.unbindBranchType()).isNull();
|
||||
|
||||
// TCC
|
||||
RpcContext.getContext().setAttachment(RootContext.KEY_XID, DEFAULT_XID);
|
||||
RpcContext.getContext().setAttachment(RootContext.KEY_BRANCH_TYPE, BranchType.TCC.name());
|
||||
filter.invoke(new MockInvoker(() -> {
|
||||
assertThat(RootContext.getXID()).isEqualTo(DEFAULT_XID);
|
||||
assertThat(RootContext.getBranchType()).isEqualTo(BranchType.TCC);
|
||||
}), null);
|
||||
assertThat(RootContext.unbind()).isNull();
|
||||
assertThat(RootContext.unbindBranchType()).isNull();
|
||||
|
||||
// TCC
|
||||
RootContext.bind(DEFAULT_XID);
|
||||
RootContext.bindBranchType(BranchType.SAGA);
|
||||
RpcContext.getContext().setAttachment(RootContext.KEY_XID, DEFAULT_XID);
|
||||
RpcContext.getContext().setAttachment(RootContext.KEY_BRANCH_TYPE, BranchType.TCC.name());
|
||||
filter.invoke(new MockInvoker(() -> {
|
||||
assertThat(RootContext.getXID()).isEqualTo(DEFAULT_XID);
|
||||
assertThat(RootContext.getBranchType()).isEqualTo(BranchType.SAGA);
|
||||
}), null);
|
||||
assertThat(RootContext.unbind()).isEqualTo(DEFAULT_XID);
|
||||
assertThat(RootContext.unbindBranchType()).isEqualTo(BranchType.SAGA);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.dubbo.alibaba.mock;
|
||||
|
||||
import com.alibaba.dubbo.common.URL;
|
||||
import com.alibaba.dubbo.rpc.Invocation;
|
||||
import com.alibaba.dubbo.rpc.Invoker;
|
||||
import com.alibaba.dubbo.rpc.Result;
|
||||
import com.alibaba.dubbo.rpc.RpcException;
|
||||
|
||||
/**
|
||||
* @author wang.liang
|
||||
*/
|
||||
public class MockInvoker implements Invoker<Object> {
|
||||
|
||||
private Runnable runnable;
|
||||
|
||||
public MockInvoker() {
|
||||
}
|
||||
|
||||
public MockInvoker(Runnable runnable) {
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Object> getInterface() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result invoke(Invocation invocation) throws RpcException {
|
||||
if (runnable != null) {
|
||||
runnable.run();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
|
||||
}
|
||||
}
|
||||
44
integration/dubbo/pom.xml
Normal file
44
integration/dubbo/pom.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 1999-2019 Seata.io Group.
|
||||
~
|
||||
~ Licensed 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">
|
||||
<parent>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-parent</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>seata-dubbo</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>seata-dubbo ${project.version}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-tm</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.dubbo;
|
||||
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.core.constants.DubboConstants;
|
||||
import io.seata.core.context.RootContext;
|
||||
import io.seata.core.model.BranchType;
|
||||
import org.apache.dubbo.common.extension.Activate;
|
||||
import org.apache.dubbo.rpc.Filter;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.Result;
|
||||
import org.apache.dubbo.rpc.RpcContext;
|
||||
import org.apache.dubbo.rpc.RpcException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The type Transaction propagation filter.
|
||||
*
|
||||
* @author sharajava
|
||||
*/
|
||||
@Activate(group = {DubboConstants.PROVIDER, DubboConstants.CONSUMER}, order = 100)
|
||||
public class ApacheDubboTransactionPropagationFilter implements Filter {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ApacheDubboTransactionPropagationFilter.class);
|
||||
|
||||
@Override
|
||||
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
|
||||
String xid = RootContext.getXID();
|
||||
BranchType branchType = RootContext.getBranchType();
|
||||
|
||||
String rpcXid = getRpcXid();
|
||||
String rpcBranchType = RpcContext.getContext().getAttachment(RootContext.KEY_BRANCH_TYPE);
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("xid in RootContext[{}] xid in RpcContext[{}]", xid, rpcXid);
|
||||
}
|
||||
boolean bind = false;
|
||||
if (xid != null) {
|
||||
RpcContext.getContext().setAttachment(RootContext.KEY_XID, xid);
|
||||
RpcContext.getContext().setAttachment(RootContext.KEY_BRANCH_TYPE, branchType.name());
|
||||
} else {
|
||||
if (rpcXid != null) {
|
||||
RootContext.bind(rpcXid);
|
||||
if (StringUtils.equals(BranchType.TCC.name(), rpcBranchType)) {
|
||||
RootContext.bindBranchType(BranchType.TCC);
|
||||
}
|
||||
bind = true;
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("bind xid [{}] branchType [{}] to RootContext", rpcXid, rpcBranchType);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
return invoker.invoke(invocation);
|
||||
} finally {
|
||||
if (bind) {
|
||||
BranchType previousBranchType = RootContext.getBranchType();
|
||||
String unbindXid = RootContext.unbind();
|
||||
if (BranchType.TCC == previousBranchType) {
|
||||
RootContext.unbindBranchType();
|
||||
}
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("unbind xid [{}] branchType [{}] from RootContext", unbindXid, previousBranchType);
|
||||
}
|
||||
if (!rpcXid.equalsIgnoreCase(unbindXid)) {
|
||||
LOGGER.warn("xid in change during RPC from {} to {},branchType from {} to {}", rpcXid, unbindXid,
|
||||
rpcBranchType != null ? rpcBranchType : "AT", previousBranchType);
|
||||
if (unbindXid != null) {
|
||||
RootContext.bind(unbindXid);
|
||||
LOGGER.warn("bind xid [{}] back to RootContext", unbindXid);
|
||||
if (BranchType.TCC == previousBranchType) {
|
||||
RootContext.bindBranchType(BranchType.TCC);
|
||||
LOGGER.warn("bind branchType [{}] back to RootContext", previousBranchType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get rpc xid
|
||||
* @return
|
||||
*/
|
||||
private String getRpcXid() {
|
||||
String rpcXid = RpcContext.getContext().getAttachment(RootContext.KEY_XID);
|
||||
if (rpcXid == null) {
|
||||
rpcXid = RpcContext.getContext().getAttachment(RootContext.KEY_XID.toLowerCase());
|
||||
}
|
||||
return rpcXid;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
io.seata.integration.dubbo.ApacheDubboTransactionPropagationFilter
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.dubbo;
|
||||
|
||||
import io.seata.core.context.RootContext;
|
||||
import io.seata.core.model.BranchType;
|
||||
import io.seata.integration.dubbo.mock.MockInvoker;
|
||||
import org.apache.dubbo.rpc.RpcContext;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author wang.liang
|
||||
*/
|
||||
public class ApacheDubboTransactionPropagationFilterTest {
|
||||
|
||||
private static final String DEFAULT_XID = "1234567890";
|
||||
|
||||
@Test
|
||||
public void testInvoke_And_RootContext() {
|
||||
ApacheDubboTransactionPropagationFilter filter = new ApacheDubboTransactionPropagationFilter();
|
||||
|
||||
// SAGA
|
||||
RpcContext.getContext().setAttachment(RootContext.KEY_XID, DEFAULT_XID);
|
||||
RpcContext.getContext().setAttachment(RootContext.KEY_BRANCH_TYPE, BranchType.SAGA.name());
|
||||
filter.invoke(new MockInvoker(() -> {
|
||||
assertThat(RootContext.getXID()).isEqualTo(DEFAULT_XID);
|
||||
assertThat(RootContext.getBranchType()).isEqualTo(BranchType.AT);
|
||||
}), null);
|
||||
assertThat(RootContext.unbind()).isNull();
|
||||
assertThat(RootContext.unbindBranchType()).isNull();
|
||||
|
||||
// TCC
|
||||
RpcContext.getContext().setAttachment(RootContext.KEY_XID, DEFAULT_XID);
|
||||
RpcContext.getContext().setAttachment(RootContext.KEY_BRANCH_TYPE, BranchType.TCC.name());
|
||||
filter.invoke(new MockInvoker(() -> {
|
||||
assertThat(RootContext.getXID()).isEqualTo(DEFAULT_XID);
|
||||
assertThat(RootContext.getBranchType()).isEqualTo(BranchType.TCC);
|
||||
}), null);
|
||||
assertThat(RootContext.unbind()).isNull();
|
||||
assertThat(RootContext.unbindBranchType()).isNull();
|
||||
|
||||
// TCC
|
||||
RootContext.bind(DEFAULT_XID);
|
||||
RootContext.bindBranchType(BranchType.SAGA);
|
||||
RpcContext.getContext().setAttachment(RootContext.KEY_XID, DEFAULT_XID);
|
||||
RpcContext.getContext().setAttachment(RootContext.KEY_BRANCH_TYPE, BranchType.TCC.name());
|
||||
filter.invoke(new MockInvoker(() -> {
|
||||
assertThat(RootContext.getXID()).isEqualTo(DEFAULT_XID);
|
||||
assertThat(RootContext.getBranchType()).isEqualTo(BranchType.SAGA);
|
||||
}), null);
|
||||
assertThat(RootContext.unbind()).isEqualTo(DEFAULT_XID);
|
||||
assertThat(RootContext.unbindBranchType()).isEqualTo(BranchType.SAGA);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.dubbo.mock;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.rpc.Invocation;
|
||||
import org.apache.dubbo.rpc.Invoker;
|
||||
import org.apache.dubbo.rpc.Result;
|
||||
import org.apache.dubbo.rpc.RpcException;
|
||||
|
||||
/**
|
||||
* @author wang.liang
|
||||
*/
|
||||
public class MockInvoker implements Invoker<Object> {
|
||||
|
||||
private Runnable runnable;
|
||||
|
||||
public MockInvoker() {
|
||||
}
|
||||
|
||||
public MockInvoker(Runnable runnable) {
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Object> getInterface() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result invoke(Invocation invocation) throws RpcException {
|
||||
if (runnable != null) {
|
||||
runnable.run();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
|
||||
}
|
||||
}
|
||||
85
integration/grpc/pom.xml
Normal file
85
integration/grpc/pom.xml
Normal file
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 1999-2019 Seata.io Group.
|
||||
~
|
||||
~ Licensed 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">
|
||||
<parent>
|
||||
<artifactId>seata-parent</artifactId>
|
||||
<groupId>io.seata</groupId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>seata-grpc</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>seata-grpc ${project.version}</name>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-tm</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-netty</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-protobuf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-stub</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-testing</artifactId>
|
||||
<scope>test</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.xolstice.maven.plugins</groupId>
|
||||
<artifactId>protobuf-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<protocArtifact>com.google.protobuf:protoc:3.3.0:exe:${os.detected.classifier}</protocArtifact>
|
||||
<pluginId>grpc-java</pluginId>
|
||||
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.8.0:exe:${os.detected.classifier}</pluginArtifact>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>test-compile</goal>
|
||||
<goal>test-compile-custom</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.grpc.interceptor;
|
||||
|
||||
import io.grpc.Metadata;
|
||||
import io.seata.core.context.RootContext;
|
||||
|
||||
/**
|
||||
* @author eddyxu1213@126.com
|
||||
*/
|
||||
public interface GrpcHeaderKey {
|
||||
|
||||
Metadata.Key<String> HEADER_KEY = Metadata.Key.of(RootContext.KEY_XID, Metadata.ASCII_STRING_MARSHALLER);
|
||||
|
||||
Metadata.Key<String> HEADER_KEY_LOWERCASE = Metadata.Key.of(RootContext.KEY_XID.toLowerCase(), Metadata.ASCII_STRING_MARSHALLER);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.grpc.interceptor.client;
|
||||
|
||||
import io.grpc.CallOptions;
|
||||
import io.grpc.Channel;
|
||||
import io.grpc.ClientCall;
|
||||
import io.grpc.ClientInterceptor;
|
||||
import io.grpc.ForwardingClientCall;
|
||||
import io.grpc.ForwardingClientCallListener;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.MethodDescriptor;
|
||||
import io.seata.core.context.RootContext;
|
||||
import io.seata.integration.grpc.interceptor.GrpcHeaderKey;
|
||||
|
||||
/**
|
||||
* @author eddyxu1213@126.com
|
||||
*/
|
||||
public class ClientTransactionInterceptor implements ClientInterceptor {
|
||||
|
||||
@Override
|
||||
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
|
||||
MethodDescriptor<ReqT, RespT> method,
|
||||
CallOptions callOptions,
|
||||
Channel next) {
|
||||
|
||||
String xid = RootContext.getXID();
|
||||
return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
|
||||
|
||||
@Override
|
||||
public void start(Listener<RespT> responseListener, Metadata headers) {
|
||||
if (xid != null) {
|
||||
headers.put(GrpcHeaderKey.HEADER_KEY, xid);
|
||||
}
|
||||
super.start(new ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT>(responseListener) {
|
||||
@Override
|
||||
public void onHeaders(Metadata headers) {
|
||||
super.onHeaders(headers);
|
||||
}
|
||||
}, headers);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.grpc.interceptor.server;
|
||||
|
||||
import io.grpc.ServerCall;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.core.context.RootContext;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author eddyxu1213@126.com
|
||||
*/
|
||||
public class ServerListenerProxy<ReqT> extends ServerCall.Listener<ReqT> {
|
||||
|
||||
private ServerCall.Listener<ReqT> target;
|
||||
private String xid;
|
||||
|
||||
public ServerListenerProxy(String xid, ServerCall.Listener<ReqT> target) {
|
||||
super();
|
||||
Objects.requireNonNull(target);
|
||||
this.target = target;
|
||||
this.xid = xid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(ReqT message) {
|
||||
target.onMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHalfClose() {
|
||||
if (StringUtils.isNotBlank(xid)) {
|
||||
RootContext.bind(xid);
|
||||
}
|
||||
target.onHalfClose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
if (StringUtils.isNotBlank(xid) && RootContext.inGlobalTransaction()) {
|
||||
RootContext.unbind();
|
||||
}
|
||||
target.onCancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
if (StringUtils.isNotBlank(xid) && RootContext.inGlobalTransaction()) {
|
||||
RootContext.unbind();
|
||||
}
|
||||
target.onComplete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReady() {
|
||||
target.onReady();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.grpc.interceptor.server;
|
||||
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.ServerCall;
|
||||
import io.grpc.ServerCallHandler;
|
||||
import io.grpc.ServerInterceptor;
|
||||
import io.seata.integration.grpc.interceptor.GrpcHeaderKey;
|
||||
|
||||
/**
|
||||
* @author eddyxu1213@126.com
|
||||
*/
|
||||
public class ServerTransactionInterceptor implements ServerInterceptor {
|
||||
|
||||
@Override
|
||||
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
|
||||
ServerCall<ReqT, RespT> serverCall,
|
||||
Metadata metadata,
|
||||
ServerCallHandler<ReqT, RespT> serverCallHandler) {
|
||||
String xid = getRpcXid(metadata);
|
||||
return new ServerListenerProxy<>(xid, serverCallHandler.startCall(serverCall, metadata));
|
||||
}
|
||||
|
||||
/**
|
||||
* get rpc xid
|
||||
* @param metadata
|
||||
* @return
|
||||
*/
|
||||
private String getRpcXid(Metadata metadata) {
|
||||
String rpcXid = metadata.get(GrpcHeaderKey.HEADER_KEY);
|
||||
if (rpcXid == null) {
|
||||
rpcXid = metadata.get(GrpcHeaderKey.HEADER_KEY_LOWERCASE);
|
||||
}
|
||||
return rpcXid;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.grpc.interceptor;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import io.grpc.ClientInterceptors;
|
||||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.Metadata;
|
||||
import io.grpc.ServerInterceptor;
|
||||
import io.grpc.ServerInterceptors;
|
||||
import io.grpc.inprocess.InProcessChannelBuilder;
|
||||
import io.grpc.inprocess.InProcessServerBuilder;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
import io.grpc.testing.GrpcCleanupRule;
|
||||
import io.seata.core.context.RootContext;
|
||||
import io.seata.integration.grpc.interceptor.client.ClientTransactionInterceptor;
|
||||
import io.seata.integration.grpc.interceptor.proto.ContextRpcGrpc;
|
||||
import io.seata.integration.grpc.interceptor.proto.Request;
|
||||
import io.seata.integration.grpc.interceptor.proto.Response;
|
||||
import io.seata.integration.grpc.interceptor.server.ServerTransactionInterceptor;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.AdditionalAnswers.delegatesTo;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* @author eddyxu1213@126.com
|
||||
*/
|
||||
public class GrpcTest {
|
||||
|
||||
@Rule
|
||||
public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
|
||||
private final ServerInterceptor mockServerInterceptor = mock(ServerInterceptor.class, delegatesTo(new ServerTransactionInterceptor()));
|
||||
|
||||
|
||||
@Test
|
||||
public void clientHeaderDeliveredToServer() throws Exception {
|
||||
|
||||
String serverName = InProcessServerBuilder.generateName();
|
||||
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
String[] context = {null};
|
||||
|
||||
//executor
|
||||
Executor executorService = new ThreadPoolExecutor(2, 2, 1, TimeUnit.HOURS, new LinkedBlockingQueue<>(), new ThreadFactory() {
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
return new Thread(r, "contextText-" + System.currentTimeMillis());
|
||||
}
|
||||
});
|
||||
|
||||
//server
|
||||
grpcCleanup.register(InProcessServerBuilder.forName(serverName).executor(executorService)
|
||||
.addService(ServerInterceptors.intercept(new ContextRpcGrpc.ContextRpcImplBase() {
|
||||
@Override
|
||||
public void contextRpc(Request request, StreamObserver<Response> responseObserver) {
|
||||
context[0] = RootContext.getXID();
|
||||
countDownLatch.countDown();
|
||||
responseObserver.onNext(Response.newBuilder().setGreet("hello! " + request.getName()).build());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
}, mockServerInterceptor))
|
||||
.build().start());
|
||||
|
||||
//client
|
||||
ManagedChannel channel = grpcCleanup.register(InProcessChannelBuilder.forName(serverName).executor(executorService).build());
|
||||
ContextRpcGrpc.ContextRpcFutureStub stub = ContextRpcGrpc.newFutureStub(
|
||||
ClientInterceptors.intercept(channel, new ClientTransactionInterceptor()));
|
||||
RootContext.bind("test_context");
|
||||
ListenableFuture<Response> future = stub.contextRpc(Request.newBuilder().setName("seata").build());
|
||||
assertEquals("hello! seata", future.get().getGreet());
|
||||
|
||||
ArgumentCaptor<Metadata> metadataCaptor = ArgumentCaptor.forClass(Metadata.class);
|
||||
verify(mockServerInterceptor).interceptCall(ArgumentMatchers.any(), metadataCaptor.capture(), ArgumentMatchers.any());
|
||||
assertEquals("test_context", metadataCaptor.getValue().get(GrpcHeaderKey.HEADER_KEY));
|
||||
|
||||
countDownLatch.await();
|
||||
assertEquals("test_context", context[0]);
|
||||
}
|
||||
}
|
||||
34
integration/grpc/src/test/proto/contextTest.proto
Normal file
34
integration/grpc/src/test/proto/contextTest.proto
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
syntax = "proto3";
|
||||
|
||||
option java_multiple_files = true;
|
||||
option java_package = "io.seata.integration.grpc.interceptor.proto";
|
||||
option java_outer_classname = "ContextRpcTest";
|
||||
|
||||
service ContextRpc {
|
||||
rpc ContextRpc (Request) returns (Response) {
|
||||
}
|
||||
}
|
||||
|
||||
message Request {
|
||||
string name = 1;
|
||||
|
||||
}
|
||||
|
||||
message Response {
|
||||
string greet = 1;
|
||||
}
|
||||
61
integration/http/pom.xml
Normal file
61
integration/http/pom.xml
Normal file
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 1999-2019 Seata.io Group.
|
||||
~
|
||||
~ Licensed 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">
|
||||
<parent>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-parent</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>seata-http</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>seata-http ${project.version}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
<version>2.5</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.http;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONException;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import io.seata.common.util.CollectionUtils;
|
||||
import io.seata.core.context.RootContext;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.Args;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Abstract http executor.
|
||||
*
|
||||
* @author wangxb
|
||||
*/
|
||||
public abstract class AbstractHttpExecutor implements HttpExecutor {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractHttpExecutor.class);
|
||||
|
||||
@Override
|
||||
public <T, K> K executePost(String host, String path, T paramObject, Class<K> returnType) throws IOException {
|
||||
|
||||
Args.notNull(returnType, "returnType");
|
||||
Args.notNull(host, "host");
|
||||
Args.notNull(path, "path");
|
||||
|
||||
CloseableHttpClient httpClient = initHttpClientInstance(paramObject);
|
||||
HttpPost httpPost = new HttpPost(host + path);
|
||||
StringEntity entity = null;
|
||||
if (paramObject != null) {
|
||||
String content;
|
||||
if (paramObject instanceof String) {
|
||||
String sParam = (String) paramObject;
|
||||
JSONObject jsonObject = null;
|
||||
try {
|
||||
jsonObject = JSON.parseObject(sParam);
|
||||
content = jsonObject.toJSONString();
|
||||
} catch (JSONException e) {
|
||||
//Interface provider process parse exception
|
||||
if (LOGGER.isWarnEnabled()) {
|
||||
LOGGER.warn(e.getMessage());
|
||||
}
|
||||
content = sParam;
|
||||
}
|
||||
|
||||
} else {
|
||||
content = JSON.toJSONString(paramObject);
|
||||
}
|
||||
entity = new StringEntity(content, ContentType.APPLICATION_JSON);
|
||||
}
|
||||
|
||||
entity = buildEntity(entity, paramObject);
|
||||
if (entity != null) {
|
||||
httpPost.setEntity(entity);
|
||||
}
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
|
||||
buildPostHeaders(headers, paramObject);
|
||||
return wrapHttpExecute(returnType, httpClient, httpPost, headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K> K executeGet(String host, String path, Map<String, String> paramObject, Class<K> returnType) throws IOException {
|
||||
|
||||
Args.notNull(returnType, "returnType");
|
||||
Args.notNull(host, "host");
|
||||
Args.notNull(path, "path");
|
||||
|
||||
CloseableHttpClient httpClient = initHttpClientInstance(paramObject);
|
||||
|
||||
HttpGet httpGet = new HttpGet(initGetUrl(host, path, paramObject));
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
|
||||
buildGetHeaders(headers, paramObject);
|
||||
return wrapHttpExecute(returnType, httpClient, httpGet, headers);
|
||||
}
|
||||
|
||||
private <T> CloseableHttpClient initHttpClientInstance(T paramObject) {
|
||||
CloseableHttpClient httpClient = HttpClients.createDefault();
|
||||
buildClientEntity(httpClient, paramObject);
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
protected abstract <T> void buildClientEntity(CloseableHttpClient httpClient, T paramObject);
|
||||
|
||||
private <K> K wrapHttpExecute(Class<K> returnType, CloseableHttpClient httpClient, HttpUriRequest httpUriRequest,
|
||||
Map<String, String> headers) throws IOException {
|
||||
CloseableHttpResponse response;
|
||||
String xid = RootContext.getXID();
|
||||
if (xid != null) {
|
||||
headers.put(RootContext.KEY_XID, xid);
|
||||
}
|
||||
if (!headers.isEmpty()) {
|
||||
headers.forEach(httpUriRequest::addHeader);
|
||||
}
|
||||
response = httpClient.execute(httpUriRequest);
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
/** 2xx is success. */
|
||||
if (statusCode < HttpStatus.SC_OK || statusCode > HttpStatus.SC_MULTI_STATUS) {
|
||||
throw new RuntimeException("Failed to invoke the http method "
|
||||
+ httpUriRequest.getURI() + " in the service "
|
||||
+ ". return status by: " + response.getStatusLine().getStatusCode());
|
||||
}
|
||||
|
||||
return convertResult(response, returnType);
|
||||
}
|
||||
|
||||
protected abstract <T> void buildGetHeaders(Map<String, String> headers, T paramObject);
|
||||
|
||||
protected abstract String initGetUrl(String host, String path, Map<String, String> paramObject);
|
||||
|
||||
|
||||
protected abstract <T> void buildPostHeaders(Map<String, String> headers, T t);
|
||||
|
||||
protected abstract <T> StringEntity buildEntity(StringEntity entity, T t);
|
||||
|
||||
protected abstract <K> K convertResult(HttpResponse response, Class<K> clazz);
|
||||
|
||||
|
||||
public static Map<String, String> convertParamOfBean(Object sourceParam) {
|
||||
return CollectionUtils.toStringMap(JSON.parseObject(JSON.toJSONString(sourceParam, SerializerFeature.WriteNullStringAsEmpty, SerializerFeature.WriteMapNullValue), Map.class));
|
||||
}
|
||||
|
||||
public static <T> Map<String, String> convertParamOfJsonString(String jsonStr, Class<T> returnType) {
|
||||
return convertParamOfBean(JSON.parseObject(jsonStr, returnType));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.http;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Default http executor.
|
||||
*
|
||||
* @author wangxb
|
||||
*/
|
||||
public class DefaultHttpExecutor extends AbstractHttpExecutor {
|
||||
|
||||
private static DefaultHttpExecutor instance = new DefaultHttpExecutor();
|
||||
|
||||
private DefaultHttpExecutor() {
|
||||
}
|
||||
|
||||
public static DefaultHttpExecutor getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void buildClientEntity(CloseableHttpClient httpClient, T paramObject) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void buildGetHeaders(Map<String, String> headers, T paramObject) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String initGetUrl(String host, String path, Map<String, String> querys) {
|
||||
|
||||
if (querys.isEmpty()) {
|
||||
return host + path;
|
||||
}
|
||||
StringBuilder sbUrl = new StringBuilder();
|
||||
sbUrl.append(host);
|
||||
if (!StringUtils.isBlank(path)) {
|
||||
sbUrl.append(path);
|
||||
}
|
||||
|
||||
StringBuilder sbQuery = new StringBuilder();
|
||||
Iterator queryKeys = querys.entrySet().iterator();
|
||||
|
||||
while (queryKeys.hasNext()) {
|
||||
Map.Entry<String, String> query = (Map.Entry) queryKeys.next();
|
||||
if (0 < sbQuery.length()) {
|
||||
sbQuery.append("&");
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
|
||||
sbQuery.append(query.getValue());
|
||||
}
|
||||
|
||||
if (!StringUtils.isBlank(query.getKey())) {
|
||||
sbQuery.append(query.getKey());
|
||||
if (!StringUtils.isBlank(query.getValue())) {
|
||||
sbQuery.append("=");
|
||||
try {
|
||||
sbQuery.append(URLEncoder.encode(query.getValue(), "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sbQuery.length() > 0) {
|
||||
sbUrl.append("?").append(sbQuery);
|
||||
}
|
||||
|
||||
return sbUrl.toString();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void buildPostHeaders(Map<String, String> headers, T t) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> StringEntity buildEntity(StringEntity entity, T t) {
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K> K convertResult(HttpResponse response, Class<K> clazz) {
|
||||
|
||||
|
||||
if (clazz == HttpResponse.class) {
|
||||
return (K) response;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Http executor.
|
||||
*
|
||||
* @author wangxb
|
||||
*/
|
||||
public interface HttpExecutor {
|
||||
|
||||
/**
|
||||
* Execute post k.
|
||||
*
|
||||
* @param <T> the type parameter
|
||||
* @param <K> the type parameter
|
||||
* @param host the host
|
||||
* @param path the path
|
||||
* @param paramObject the param object
|
||||
* @param returnType the return type
|
||||
* @return the k
|
||||
* @throws IOException the io exception
|
||||
*/
|
||||
<T, K> K executePost(String host, String path, T paramObject, Class<K> returnType) throws IOException;
|
||||
|
||||
/**
|
||||
* get method only support param type of Map<String,String>
|
||||
*
|
||||
* @param <K> the type parameter
|
||||
* @param host the host
|
||||
* @param path the path
|
||||
* @param paramObject the param object
|
||||
* @param returnType the return type
|
||||
* @return K k
|
||||
* @throws IOException the io exception
|
||||
*/
|
||||
<K> K executeGet(String host, String path, Map<String, String> paramObject, Class<K> returnType) throws IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.http;
|
||||
|
||||
import io.seata.core.context.RootContext;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Http exception handle.
|
||||
*
|
||||
* @author wangxb
|
||||
*/
|
||||
public class HttpHandlerExceptionResolver extends AbstractHandlerExceptionResolver {
|
||||
|
||||
|
||||
@Override
|
||||
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse httpServletResponse, Object o, Exception e) {
|
||||
|
||||
XidResource.cleanXid(request.getHeader(RootContext.KEY_XID));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.http;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import io.seata.core.context.RootContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||
|
||||
|
||||
/**
|
||||
* Springmvc Intercepter.
|
||||
*
|
||||
* @author wangxb
|
||||
*/
|
||||
public class TransactionPropagationInterceptor extends HandlerInterceptorAdapter {
|
||||
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TransactionPropagationInterceptor.class);
|
||||
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
String xid = RootContext.getXID();
|
||||
String rpcXid = request.getHeader(RootContext.KEY_XID);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("xid in RootContext[{}] xid in HttpContext[{}]", xid, rpcXid);
|
||||
}
|
||||
if (xid == null && rpcXid != null) {
|
||||
RootContext.bind(rpcXid);
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("bind[{}] to RootContext", rpcXid);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
|
||||
ModelAndView modelAndView) {
|
||||
if (RootContext.inGlobalTransaction()) {
|
||||
XidResource.cleanXid(request.getHeader(RootContext.KEY_XID));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.http;
|
||||
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.core.context.RootContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Xid handler.
|
||||
*
|
||||
* @author wangxb
|
||||
*/
|
||||
public class XidResource {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(XidResource.class);
|
||||
|
||||
|
||||
public static void cleanXid(String rpcXid) {
|
||||
String xid = RootContext.getXID();
|
||||
if (xid != null) {
|
||||
String unbindXid = RootContext.unbind();
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("unbind[{}] from RootContext", unbindXid);
|
||||
}
|
||||
if (!StringUtils.equalsIgnoreCase(rpcXid, unbindXid)) {
|
||||
LOGGER.warn("xid in change during RPC from {} to {}", rpcXid, unbindXid);
|
||||
if (unbindXid != null) {
|
||||
RootContext.bind(unbindXid);
|
||||
LOGGER.warn("bind [{}] back to RootContext", unbindXid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.http;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import io.seata.core.context.RootContext;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static io.seata.integration.http.AbstractHttpExecutor.convertParamOfBean;
|
||||
import static io.seata.integration.http.AbstractHttpExecutor.convertParamOfJsonString;
|
||||
|
||||
/**
|
||||
* @author wangxb
|
||||
*/
|
||||
class HttpTest {
|
||||
|
||||
private static final String host = "http://127.0.0.1:8081";
|
||||
private static final String testException = "/testException";
|
||||
private static final String getPath = "/testGet";
|
||||
private static final String postPath = "/testPost";
|
||||
public static final String XID = "127.0.0.1:8081:87654321";
|
||||
private static final int PARAM_TYPE_MAP = 1;
|
||||
private static final int PARAM_TYPE_BEAN = 2;
|
||||
|
||||
@Test
|
||||
void testGetProviderXID() {
|
||||
RootContext.bind(XID);
|
||||
providerStart();
|
||||
String result = consumerGetStart(PARAM_TYPE_MAP);
|
||||
Assertions.assertEquals("Person{name='zhangsan', age=15}", result);
|
||||
RootContext.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPostProviderXID() {
|
||||
RootContext.bind(XID);
|
||||
providerStart();
|
||||
String result = consumerPostStart(PARAM_TYPE_MAP);
|
||||
Assertions.assertEquals("Person{name='zhangsan', age=15}", result);
|
||||
RootContext.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetExceptionRemoveXID() {
|
||||
RootContext.bind(XID);
|
||||
providerStart();
|
||||
String result = consumerGetExceptionStart();
|
||||
Assertions.assertEquals("Callee remove local xid success", result);
|
||||
RootContext.unbind();
|
||||
}
|
||||
|
||||
public void providerStart() {
|
||||
new MockWebServer().start(8081);
|
||||
}
|
||||
|
||||
public static class Person {
|
||||
private String name;
|
||||
private int age;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Person{" +
|
||||
"name='" + name + '\'' +
|
||||
", age=" + age +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
private String consumerPostStart(int param_type) {
|
||||
DefaultHttpExecutor httpExecuter = DefaultHttpExecutor.getInstance();
|
||||
String str = "{\n" +
|
||||
" \"name\":\"zhangsan\",\n" +
|
||||
" \"age\":15\n" +
|
||||
"}";
|
||||
Person person = JSON.parseObject(str, Person.class);
|
||||
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("name", "zhangsan");
|
||||
map.put("age", 15);
|
||||
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("name", "zhangsan");
|
||||
json.put("age", 15);
|
||||
|
||||
//The body parameter of post supports the above types (str,person,map,json)
|
||||
try {
|
||||
HttpResponse response;
|
||||
|
||||
if (param_type == PARAM_TYPE_MAP) {
|
||||
response = httpExecuter.executePost(host, postPath, map, HttpResponse.class);
|
||||
} else if (param_type == PARAM_TYPE_BEAN) {
|
||||
response = httpExecuter.executePost(host, postPath, person, HttpResponse.class);
|
||||
} else {
|
||||
response = httpExecuter.executePost(host, postPath, str, HttpResponse.class);
|
||||
}
|
||||
|
||||
return readStreamAsStr(response.getEntity().getContent());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String consumerGetStart(int param_type) {
|
||||
DefaultHttpExecutor httpExecuter = DefaultHttpExecutor.getInstance();
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("name", "zhangsan");
|
||||
params.put("age", "15");
|
||||
|
||||
String str = "{\n" +
|
||||
" \"name\":\"zhangsan\",\n" +
|
||||
" \"age\":15\n" +
|
||||
"}";
|
||||
Person person = JSON.parseObject(str, Person.class);
|
||||
try {
|
||||
//support all type of parameter types
|
||||
HttpResponse response;
|
||||
if (param_type == PARAM_TYPE_MAP) {
|
||||
response = httpExecuter.executeGet(host, getPath, params, HttpResponse.class);
|
||||
} else if (param_type == PARAM_TYPE_BEAN) {
|
||||
response = httpExecuter.executeGet(host, getPath, convertParamOfBean(person), HttpResponse.class);
|
||||
} else {
|
||||
response = httpExecuter.executeGet(host, getPath, convertParamOfJsonString(str, Person.class), HttpResponse.class);
|
||||
}
|
||||
return readStreamAsStr(response.getEntity().getContent());
|
||||
|
||||
} catch (IOException e) {
|
||||
/* if in Travis CI env, only mock method call */
|
||||
MockHttpExecuter mockHttpExecuter = new MockHttpExecuter();
|
||||
try {
|
||||
return mockHttpExecuter.executeGet(host, getPath, params, String.class);
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String consumerGetExceptionStart() {
|
||||
DefaultHttpExecutor httpExecuter = DefaultHttpExecutor.getInstance();
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("name", "zhangsan");
|
||||
params.put("age", "15");
|
||||
HttpResponse response;
|
||||
try {
|
||||
response = httpExecuter.executeGet(host, testException, params, HttpResponse.class);
|
||||
return readStreamAsStr(response.getEntity().getContent());
|
||||
} catch (IOException e) {
|
||||
/* if in Travis CI inv, only mock method call */
|
||||
MockHttpExecuter mockHttpExecuter = new MockHttpExecuter();
|
||||
try {
|
||||
return mockHttpExecuter.executeGet(host, testException, params, String.class);
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static String readStreamAsStr(InputStream is) throws IOException {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
WritableByteChannel dest = Channels.newChannel(bos);
|
||||
ReadableByteChannel src = Channels.newChannel(is);
|
||||
ByteBuffer bb = ByteBuffer.allocate(4096);
|
||||
|
||||
while (src.read(bb) != -1) {
|
||||
bb.flip();
|
||||
dest.write(bb);
|
||||
bb.clear();
|
||||
}
|
||||
|
||||
src.close();
|
||||
dest.close();
|
||||
return new String(bos.toByteArray(), "UTF-8");
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertParamOfJsonStringTest() {
|
||||
|
||||
String targetParam = "{name=zhangsan, age=15}";
|
||||
String str = "{\n" +
|
||||
" \"name\":\"zhangsan\",\n" +
|
||||
" \"age\":15\n" +
|
||||
"}";
|
||||
Map<String, String> map = convertParamOfJsonString(str, Person.class);
|
||||
Assertions.assertEquals(map.toString(), targetParam);
|
||||
Person person = JSON.parseObject(str, Person.class);
|
||||
map = convertParamOfBean(person);
|
||||
Assertions.assertEquals(map.toString(), targetParam);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.http;
|
||||
|
||||
import io.seata.core.context.RootContext;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
/**
|
||||
* @author : wangxb
|
||||
* @Description: Mock springmvc controller
|
||||
*/
|
||||
@Controller
|
||||
public class MockController {
|
||||
|
||||
|
||||
@RequestMapping("/testGet")
|
||||
@ResponseBody
|
||||
public String testGet(HttpTest.Person person) {
|
||||
/* verify xid propagate by test case */
|
||||
Assertions.assertEquals(HttpTest.XID,RootContext.getXID());
|
||||
return person.toString();
|
||||
}
|
||||
|
||||
|
||||
@ResponseBody
|
||||
@PostMapping("/testPost")
|
||||
public String testPost(@RequestBody HttpTest.Person person) {
|
||||
/* verify xid propagate by test case */
|
||||
Assertions.assertEquals(HttpTest.XID,RootContext.getXID());
|
||||
return person.toString();
|
||||
}
|
||||
|
||||
@RequestMapping("/testException")
|
||||
@ResponseBody
|
||||
public String testException(HttpTest.Person person) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.http;
|
||||
|
||||
import io.seata.core.context.RootContext;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.util.Args;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author : wangxb
|
||||
*/
|
||||
public class MockHttpExecuter extends AbstractHttpExecutor {
|
||||
|
||||
DefaultHttpExecutor httpExecutor = DefaultHttpExecutor.getInstance();
|
||||
|
||||
@Override
|
||||
public <K> K executeGet(String host, String path, Map<String, String> paramObject, Class<K> returnType) throws IOException {
|
||||
Args.notNull(host, "host");
|
||||
Args.notNull(path, "path");
|
||||
|
||||
String getUrl = initGetUrl(host, path, paramObject);
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
|
||||
MockRequest mockRequest = new MockRequest(getUrl, headers, null, path, "get");
|
||||
MockResponse mockResponse = new MockResponse(null);
|
||||
String xid = RootContext.getXID();
|
||||
if (xid != null) {
|
||||
headers.put(RootContext.KEY_XID, xid);
|
||||
}
|
||||
MockWebServer webServer = new MockWebServer();
|
||||
webServer.initServletMapping();
|
||||
return (K) webServer.dispatch(mockRequest, mockResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> void buildClientEntity(CloseableHttpClient httpClient, T paramObject) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> void buildGetHeaders(Map<String, String> headers, T paramObject) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String initGetUrl(String host, String path, Map<String, String> paramObject) {
|
||||
return httpExecutor.initGetUrl(host, path, paramObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> void buildPostHeaders(Map<String, String> headers, T t) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> StringEntity buildEntity(StringEntity entity, T t) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <K> K convertResult(HttpResponse response, Class<K> clazz) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.http;
|
||||
|
||||
import io.seata.core.context.RootContext;
|
||||
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.BufferedReader;
|
||||
import java.security.Principal;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author : wangxb
|
||||
*/
|
||||
public class MockHttpServletRequest implements HttpServletRequest {
|
||||
|
||||
private MockRequest myRequest;
|
||||
|
||||
public MockHttpServletRequest(MockRequest myRequest) {
|
||||
this.myRequest = myRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cookie[] getCookies() {
|
||||
return new Cookie[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDateHeader(String name) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name) {
|
||||
if (RootContext.KEY_XID.equals(name))
|
||||
return myRequest.getHeader().get(RootContext.KEY_XID);
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration getHeaders(String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration getHeaderNames() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntHeader(String name) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPathInfo() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPathTranslated() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContextPath() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryString() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteUser() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserInRole(String role) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getUserPrincipal() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestedSessionId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestURI() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringBuffer getRequestURL() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServletPath() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpSession getSession(boolean create) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpSession getSession() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRequestedSessionIdValid() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRequestedSessionIdFromCookie() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRequestedSessionIdFromURL() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRequestedSessionIdFromUrl() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration getAttributeNames() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCharacterEncoding() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCharacterEncoding(String env) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getContentLength() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletInputStream getInputStream() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParameter(String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration getParameterNames() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getParameterValues(String name) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map getParameterMap() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocol() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getScheme() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServerName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getServerPort() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedReader getReader() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteAddr() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteHost() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, Object o) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale getLocale() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration getLocales() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestDispatcher getRequestDispatcher(String path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRealPath(String path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemotePort() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocalName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocalAddr() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLocalPort() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.http;
|
||||
|
||||
import io.seata.core.context.RootContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author : wangxb
|
||||
*/
|
||||
public class MockRequest {
|
||||
private String url;
|
||||
private Map<String, String> header = new HashMap<>();
|
||||
private String body;
|
||||
private String path;
|
||||
private String method = "get";
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public Map<String, String> getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public MockRequest(String url, Map<String, String> header, String body, String path,String method) {
|
||||
|
||||
this.url = url;
|
||||
this.header = header;
|
||||
this.body = body;
|
||||
this.path = path;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public MockRequest(InputStream inputStream) throws IOException {
|
||||
String httpRequest = "";
|
||||
byte[] httpRequestBytes = new byte[2048];
|
||||
int length = 0;
|
||||
if ((length = inputStream.read(httpRequestBytes)) > 0) {
|
||||
httpRequest = new String(httpRequestBytes, 0, length);
|
||||
}
|
||||
|
||||
String httpHead = httpRequest.split("\n")[0];
|
||||
url = httpHead.split("\\s")[1];
|
||||
String xid = httpRequest.split("\\n")[1];
|
||||
if (xid.startsWith(RootContext.KEY_XID)) {
|
||||
xid = xid.split(RootContext.KEY_XID + ":")[1].trim();
|
||||
}
|
||||
header.put(RootContext.KEY_XID, xid);
|
||||
|
||||
path = url.split("\\?")[0];
|
||||
if (httpRequest.startsWith("POST")) {
|
||||
body = httpRequest.split("\\n")[9];
|
||||
method = "post";
|
||||
}
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.http;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* @author : wangxb
|
||||
*/
|
||||
public class MockResponse {
|
||||
|
||||
private OutputStream outputStream;
|
||||
|
||||
public MockResponse(OutputStream outputStream) {
|
||||
this.outputStream = outputStream;
|
||||
}
|
||||
|
||||
public String write(String content) throws IOException {
|
||||
StringBuilder httpResponse = new StringBuilder();
|
||||
httpResponse.append("HTTP/1.1 200 OK\n")
|
||||
.append("Content-Type:application/json\n")
|
||||
.append("\r\n")
|
||||
.append(content);
|
||||
if (outputStream == null) {
|
||||
//local call
|
||||
return content;
|
||||
}
|
||||
else {
|
||||
outputStream.write(httpResponse.toString().getBytes());
|
||||
outputStream.close();
|
||||
return "success";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.http;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.core.context.RootContext;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static io.seata.integration.http.AbstractHttpExecutor.convertParamOfJsonString;
|
||||
|
||||
/**
|
||||
* @author : wangxb
|
||||
*/
|
||||
public class MockWebServer {
|
||||
|
||||
private Map<String, String> urlServletMap = new HashMap<>();
|
||||
|
||||
|
||||
public void start(int port) {
|
||||
initServletMapping();
|
||||
new Thread(() -> {
|
||||
ServerSocket serverSocket = null;
|
||||
try {
|
||||
serverSocket = new ServerSocket(port);
|
||||
Socket socket = serverSocket.accept();
|
||||
InputStream inputStream = socket.getInputStream();
|
||||
OutputStream outputStream = socket.getOutputStream();
|
||||
|
||||
MockRequest myRequest = new MockRequest(inputStream);
|
||||
MockResponse myResponse = new MockResponse(outputStream);
|
||||
|
||||
dispatch(myRequest, myResponse);
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (serverSocket != null) {
|
||||
try {
|
||||
serverSocket.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
}
|
||||
|
||||
public void initServletMapping() {
|
||||
for (ServletMapping servletMapping : ServletMapping.servletMappingList) {
|
||||
urlServletMap.put(servletMapping.getPath(), servletMapping.getClazz() + "_" + servletMapping.getMethod());
|
||||
}
|
||||
}
|
||||
|
||||
public String dispatch(MockRequest myRequest, MockResponse mockResponse) {
|
||||
String clazz = urlServletMap.get(myRequest.getPath()).split("_")[0];
|
||||
String methodName = urlServletMap.get(myRequest.getPath()).split("_")[1];
|
||||
HttpServletRequest request = new MockHttpServletRequest(myRequest);
|
||||
try {
|
||||
Class<MockController> myServletClass = (Class<MockController>) Class.forName(clazz);
|
||||
MockController myServlet = myServletClass.newInstance();
|
||||
HttpTest.Person person = boxing(myRequest);
|
||||
Method method = myServletClass.getDeclaredMethod(methodName, HttpTest.Person.class);
|
||||
|
||||
/* mock request intercepter */
|
||||
TransactionPropagationInterceptor intercepter = new TransactionPropagationInterceptor();
|
||||
|
||||
intercepter.preHandle(request, null, null);
|
||||
Object result = method.invoke(myServlet, person);
|
||||
|
||||
return mockResponse.write(result.toString());
|
||||
} catch (Exception e) {
|
||||
HttpHandlerExceptionResolver resolver = new HttpHandlerExceptionResolver();
|
||||
resolver.doResolveException(request, null, null, e);
|
||||
if (RootContext.getXID() == null) {
|
||||
try {
|
||||
return mockResponse.write("Callee remove local xid success");
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private HttpTest.Person boxing(MockRequest myRequest) {
|
||||
Map params = null;
|
||||
if ("get".equals(myRequest.getMethod()))
|
||||
params = getUrlParams(myRequest.getUrl());
|
||||
else if ("post".equals(myRequest.getMethod())) {
|
||||
params = getBodyParams(myRequest.getBody());
|
||||
}
|
||||
return JSONObject.parseObject(JSONObject.toJSONString(params), HttpTest.Person.class);
|
||||
}
|
||||
|
||||
private Map<String, String> getBodyParams(String body) {
|
||||
Map<String, String> map = convertParamOfJsonString(body, HttpTest.Person.class);
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
public static Map<String, Object> getUrlParams(String param) {
|
||||
Map<String, Object> map = new HashMap<String, Object>(0);
|
||||
if (StringUtils.isBlank(param)) {
|
||||
return map;
|
||||
}
|
||||
String[] urlPath = param.split("\\?");
|
||||
if (urlPath.length < 2) {
|
||||
return map;
|
||||
}
|
||||
|
||||
String[] params = urlPath[1].split("&");
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
String[] p = params[i].split("=");
|
||||
if (p.length == 2) {
|
||||
map.put(p[0], p[1]);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.http;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author : wangxb
|
||||
*/
|
||||
public class ServletMapping {
|
||||
|
||||
public static List<ServletMapping> servletMappingList = new ArrayList<>();
|
||||
|
||||
static {
|
||||
servletMappingList.add(new ServletMapping("/testGet", "testGet", "io.seata.integration.http.MockController"));
|
||||
servletMappingList.add(new ServletMapping("/testPost", "testPost", "io.seata.integration.http.MockController"));
|
||||
servletMappingList.add(new ServletMapping("/testException", "testException", "io.seata.integration.http.MockController"));
|
||||
}
|
||||
|
||||
private String method;
|
||||
private String path;
|
||||
private String clazz;
|
||||
|
||||
public ServletMapping(String path, String method, String clazz) {
|
||||
this.method = method;
|
||||
this.path = path;
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public void setMethod(String method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public String getClazz() {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
public void setClazz(String clazz) {
|
||||
this.clazz = clazz;
|
||||
}
|
||||
}
|
||||
47
integration/motan/pom.xml
Normal file
47
integration/motan/pom.xml
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 1999-2019 Seata.io Group.
|
||||
~
|
||||
~ Licensed 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">
|
||||
<parent>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-parent</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>seata-motan</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>seata-motan ${project.version}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-tm</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.weibo</groupId>
|
||||
<artifactId>motan-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.weibo</groupId>
|
||||
<artifactId>motan-transport-netty</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.motan;
|
||||
|
||||
import com.weibo.api.motan.common.MotanConstants;
|
||||
import com.weibo.api.motan.core.extension.Activation;
|
||||
import com.weibo.api.motan.core.extension.Scope;
|
||||
import com.weibo.api.motan.core.extension.Spi;
|
||||
import com.weibo.api.motan.filter.Filter;
|
||||
import com.weibo.api.motan.rpc.Caller;
|
||||
import com.weibo.api.motan.rpc.Request;
|
||||
import com.weibo.api.motan.rpc.Response;
|
||||
import io.seata.core.context.RootContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author slievrly
|
||||
*/
|
||||
@Spi(scope = Scope.SINGLETON)
|
||||
@Activation(key = {MotanConstants.NODE_TYPE_SERVICE, MotanConstants.NODE_TYPE_REFERER}, sequence = 100)
|
||||
public class MotanTransactionFilter implements Filter {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(MotanTransactionFilter.class);
|
||||
|
||||
public MotanTransactionFilter(){}
|
||||
@Override
|
||||
public Response filter(final Caller<?> caller, final Request request) {
|
||||
String currentXid = RootContext.getXID();
|
||||
String requestXid = getRpcXid(request);
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("xid in RootContext [" + currentXid + "] xid in Request [" + requestXid + "]");
|
||||
}
|
||||
boolean bind = false;
|
||||
if (currentXid != null) {
|
||||
request.getAttachments().put(RootContext.KEY_XID, currentXid);
|
||||
} else if (requestXid != null) {
|
||||
RootContext.bind(requestXid);
|
||||
bind = true;
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("bind [" + requestXid + "] to RootContext");
|
||||
}
|
||||
|
||||
}
|
||||
try {
|
||||
return caller.call(request);
|
||||
} finally {
|
||||
if (bind) {
|
||||
String unbindXid = RootContext.unbind();
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("unbind [" + unbindXid + "] from RootContext");
|
||||
}
|
||||
if (!requestXid.equalsIgnoreCase(unbindXid)) {
|
||||
LOGGER.warn("xid has changed, during RPC from " + requestXid + " to " + unbindXid);
|
||||
if (unbindXid != null) {
|
||||
RootContext.bind(unbindXid);
|
||||
LOGGER.warn("bind [" + unbindXid + "] back to RootContext");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get rpc xid
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
private String getRpcXid(Request request) {
|
||||
String rpcXid = request.getAttachments().get(RootContext.KEY_XID);
|
||||
if (rpcXid == null) {
|
||||
rpcXid = request.getAttachments().get(RootContext.KEY_XID.toLowerCase());
|
||||
}
|
||||
return rpcXid;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
io.seata.integration.motan.MotanTransactionFilter
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.motan;
|
||||
|
||||
import com.weibo.api.motan.config.ProtocolConfig;
|
||||
import com.weibo.api.motan.config.RefererConfig;
|
||||
import com.weibo.api.motan.config.RegistryConfig;
|
||||
import com.weibo.api.motan.config.ServiceConfig;
|
||||
import io.seata.core.context.RootContext;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author slievrly
|
||||
*/
|
||||
class MotanTransactionFilterTest {
|
||||
|
||||
private static final String SERVICE_GROUP = "motan";
|
||||
private static final String SERVICE_VERSION = "1.0.0";
|
||||
private static final int SERVICE_PORT = 8004;
|
||||
private static final String PROTOCOL_ID = "motan";
|
||||
private static final String PROTOCOL_NAME = "motan";
|
||||
private static final String XID = "127.0.0.1:8091:87654321";
|
||||
private static final int REQUEST_TIMEOUT = 1000;
|
||||
|
||||
@Test
|
||||
void testGetProviderXID() {
|
||||
RootContext.bind(XID);
|
||||
providerStart();
|
||||
consumerStart();
|
||||
RootContext.unbind();
|
||||
}
|
||||
|
||||
public void providerStart() {
|
||||
ServiceConfig<XIDService> serviceConfig = new ServiceConfig<>();
|
||||
serviceConfig.setInterface(XIDService.class);
|
||||
serviceConfig.setRef(new XIDServiceImpl());
|
||||
serviceConfig.setGroup(SERVICE_GROUP);
|
||||
serviceConfig.setVersion(SERVICE_VERSION);
|
||||
RegistryConfig registryConfig = new RegistryConfig();
|
||||
registryConfig.setRegProtocol("local");
|
||||
registryConfig.setCheck(false);
|
||||
serviceConfig.setRegistry(registryConfig);
|
||||
ProtocolConfig protocol = new ProtocolConfig();
|
||||
protocol.setId(PROTOCOL_ID);
|
||||
protocol.setName(PROTOCOL_NAME);
|
||||
serviceConfig.setProtocol(protocol);
|
||||
serviceConfig.setExport("motan:" + SERVICE_PORT);
|
||||
serviceConfig.export();
|
||||
}
|
||||
|
||||
private void consumerStart() {
|
||||
RefererConfig<XIDService> refererConfig = new RefererConfig<>();
|
||||
refererConfig.setInterface(XIDService.class);
|
||||
refererConfig.setGroup(SERVICE_GROUP);
|
||||
refererConfig.setVersion(SERVICE_VERSION);
|
||||
refererConfig.setRequestTimeout(REQUEST_TIMEOUT);
|
||||
RegistryConfig registry = new RegistryConfig();
|
||||
refererConfig.setRegistry(registry);
|
||||
ProtocolConfig protocol = new ProtocolConfig();
|
||||
protocol.setId(PROTOCOL_ID);
|
||||
protocol.setName(PROTOCOL_NAME);
|
||||
refererConfig.setProtocol(protocol);
|
||||
refererConfig.setDirectUrl("localhost:" + SERVICE_PORT);
|
||||
XIDService service = refererConfig.getRef();
|
||||
Assertions.assertEquals(service.getXid(), XID);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.motan;
|
||||
|
||||
/**
|
||||
* @author slievrly
|
||||
*/
|
||||
public interface XIDService {
|
||||
String getXid();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.motan;
|
||||
|
||||
import io.seata.core.context.RootContext;
|
||||
|
||||
/**
|
||||
* @author slievrly
|
||||
*/
|
||||
public class XIDServiceImpl implements XIDService {
|
||||
|
||||
@Override
|
||||
public String getXid() {
|
||||
return RootContext.getXID();
|
||||
}
|
||||
}
|
||||
44
integration/sofa-rpc/pom.xml
Normal file
44
integration/sofa-rpc/pom.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 1999-2019 Seata.io Group.
|
||||
~
|
||||
~ Licensed 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">
|
||||
<parent>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-parent</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>seata-sofa-rpc</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>seata-sofa-rpc ${project.version}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-tm</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alipay.sofa</groupId>
|
||||
<artifactId>sofa-rpc-all</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.sofa.rpc;
|
||||
|
||||
import com.alipay.sofa.rpc.context.RpcInternalContext;
|
||||
import com.alipay.sofa.rpc.core.exception.SofaRpcException;
|
||||
import com.alipay.sofa.rpc.core.request.SofaRequest;
|
||||
import com.alipay.sofa.rpc.core.response.SofaResponse;
|
||||
import com.alipay.sofa.rpc.ext.Extension;
|
||||
import com.alipay.sofa.rpc.filter.AutoActive;
|
||||
import com.alipay.sofa.rpc.filter.Filter;
|
||||
import com.alipay.sofa.rpc.filter.FilterInvoker;
|
||||
import io.seata.core.context.RootContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* TransactionContext on consumer side.
|
||||
*
|
||||
* @author Geng Zhang
|
||||
* @since 0.6.0
|
||||
*/
|
||||
@Extension(value = "transactionContextConsumer")
|
||||
@AutoActive(consumerSide = true)
|
||||
public class TransactionContextConsumerFilter extends Filter {
|
||||
|
||||
/**
|
||||
* Logger for this class
|
||||
*/
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TransactionContextConsumerFilter.class);
|
||||
|
||||
@Override
|
||||
public SofaResponse invoke(FilterInvoker filterInvoker, SofaRequest sofaRequest) throws SofaRpcException {
|
||||
String xid = RootContext.getXID();
|
||||
String rpcXid = getRpcXid();
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("xid in RootContext[" + xid + "] xid in RpcContext[" + rpcXid + "]");
|
||||
}
|
||||
boolean bind = false;
|
||||
if (xid != null) {
|
||||
sofaRequest.addRequestProp(RootContext.KEY_XID, xid);
|
||||
} else {
|
||||
if (rpcXid != null) {
|
||||
RootContext.bind(rpcXid);
|
||||
bind = true;
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("bind[" + rpcXid + "] to RootContext");
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
return filterInvoker.invoke(sofaRequest);
|
||||
} finally {
|
||||
if (bind) {
|
||||
String unbindXid = RootContext.unbind();
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("unbind[" + unbindXid + "] from RootContext");
|
||||
}
|
||||
if (!rpcXid.equalsIgnoreCase(unbindXid)) {
|
||||
if (LOGGER.isWarnEnabled()) {
|
||||
LOGGER.warn("xid in change during RPC from " + rpcXid + " to " + unbindXid);
|
||||
}
|
||||
if (unbindXid != null) {
|
||||
RootContext.bind(unbindXid);
|
||||
if (LOGGER.isWarnEnabled()) {
|
||||
LOGGER.warn("bind [" + unbindXid + "] back to RootContext");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get rpc xid
|
||||
* @return
|
||||
*/
|
||||
private String getRpcXid() {
|
||||
String rpcXid = (String) RpcInternalContext.getContext().getAttachment(RootContext.KEY_XID);
|
||||
if (rpcXid == null) {
|
||||
rpcXid = (String) RpcInternalContext.getContext().getAttachment(RootContext.KEY_XID.toLowerCase());
|
||||
}
|
||||
return rpcXid;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.sofa.rpc;
|
||||
|
||||
import com.alipay.sofa.rpc.context.RpcInternalContext;
|
||||
import com.alipay.sofa.rpc.core.exception.SofaRpcException;
|
||||
import com.alipay.sofa.rpc.core.request.SofaRequest;
|
||||
import com.alipay.sofa.rpc.core.response.SofaResponse;
|
||||
import com.alipay.sofa.rpc.ext.Extension;
|
||||
import com.alipay.sofa.rpc.filter.AutoActive;
|
||||
import com.alipay.sofa.rpc.filter.Filter;
|
||||
import com.alipay.sofa.rpc.filter.FilterInvoker;
|
||||
import io.seata.core.context.RootContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* TransactionContext on provider side.
|
||||
*
|
||||
* @author Geng Zhang
|
||||
* @since 0.6.0
|
||||
*/
|
||||
@Extension(value = "transactionContextProvider")
|
||||
@AutoActive(providerSide = true)
|
||||
public class TransactionContextProviderFilter extends Filter {
|
||||
|
||||
/**
|
||||
* Logger for this class
|
||||
*/
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TransactionContextProviderFilter.class);
|
||||
|
||||
@Override
|
||||
public SofaResponse invoke(FilterInvoker filterInvoker, SofaRequest sofaRequest) throws SofaRpcException {
|
||||
String xid = RootContext.getXID();
|
||||
String rpcXid = getRpcXid(sofaRequest);
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("xid in RootContext[" + xid + "] xid in RpcContext[" + rpcXid + "]");
|
||||
}
|
||||
boolean bind = false;
|
||||
if (xid != null) {
|
||||
RpcInternalContext.getContext().setAttachment(RootContext.KEY_XID, xid);
|
||||
} else {
|
||||
if (rpcXid != null) {
|
||||
RootContext.bind(rpcXid);
|
||||
bind = true;
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("bind[" + rpcXid + "] to RootContext");
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
return filterInvoker.invoke(sofaRequest);
|
||||
} finally {
|
||||
if (bind) {
|
||||
String unbindXid = RootContext.unbind();
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("unbind[" + unbindXid + "] from RootContext");
|
||||
}
|
||||
if (!rpcXid.equalsIgnoreCase(unbindXid)) {
|
||||
if (LOGGER.isWarnEnabled()) {
|
||||
LOGGER.warn("xid in change during RPC from " + rpcXid + " to " + unbindXid);
|
||||
}
|
||||
if (unbindXid != null) {
|
||||
RootContext.bind(unbindXid);
|
||||
if (LOGGER.isWarnEnabled()) {
|
||||
LOGGER.warn("bind [" + unbindXid + "] back to RootContext");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get rpc xid
|
||||
* @return
|
||||
*/
|
||||
private String getRpcXid(SofaRequest sofaRequest) {
|
||||
String rpcXid = (String) sofaRequest.getRequestProp(RootContext.KEY_XID);
|
||||
if (rpcXid == null) {
|
||||
rpcXid = (String) sofaRequest.getRequestProp(RootContext.KEY_XID.toLowerCase());
|
||||
}
|
||||
return rpcXid;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
io.seata.integration.sofa.rpc.TransactionContextProviderFilter
|
||||
io.seata.integration.sofa.rpc.TransactionContextConsumerFilter
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.sofa.rpc;
|
||||
|
||||
/**
|
||||
* @author Geng Zhang
|
||||
*/
|
||||
public interface HelloService {
|
||||
|
||||
String sayHello(String name, int age);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.sofa.rpc;
|
||||
|
||||
import io.seata.core.context.RootContext;
|
||||
|
||||
/**
|
||||
* @author Geng Zhang
|
||||
*/
|
||||
public class HelloServiceImpl implements HelloService {
|
||||
|
||||
private String result;
|
||||
|
||||
private String xid;
|
||||
|
||||
public HelloServiceImpl() {
|
||||
|
||||
}
|
||||
|
||||
public HelloServiceImpl(String result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sayHello(String name, int age) {
|
||||
xid = RootContext.getXID();
|
||||
return result != null ? result : "hello " + name + " from server! age: " + age;
|
||||
}
|
||||
|
||||
public String getXid() {
|
||||
return xid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.sofa.rpc;
|
||||
|
||||
import io.seata.core.context.RootContext;
|
||||
|
||||
/**
|
||||
* @author Geng Zhang
|
||||
*/
|
||||
public class HelloServiceProxy implements HelloService {
|
||||
|
||||
private String xid;
|
||||
|
||||
private HelloService proxy;
|
||||
|
||||
public HelloServiceProxy(HelloService proxy) {
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sayHello(String name, int age) {
|
||||
xid = RootContext.getXID();
|
||||
return proxy.sayHello(name, age);
|
||||
}
|
||||
|
||||
public String getXid() {
|
||||
return xid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* Licensed 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 io.seata.integration.sofa.rpc;
|
||||
|
||||
import com.alipay.sofa.rpc.config.ConsumerConfig;
|
||||
import com.alipay.sofa.rpc.config.ProviderConfig;
|
||||
import com.alipay.sofa.rpc.config.ServerConfig;
|
||||
import com.alipay.sofa.rpc.context.RpcInternalContext;
|
||||
import com.alipay.sofa.rpc.context.RpcInvokeContext;
|
||||
import com.alipay.sofa.rpc.context.RpcRunningState;
|
||||
import com.alipay.sofa.rpc.context.RpcRuntimeContext;
|
||||
import com.alipay.sofa.rpc.core.exception.SofaRpcException;
|
||||
import io.seata.core.context.RootContext;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author Geng Zhang
|
||||
*/
|
||||
public class TransactionContextFilterTest {
|
||||
|
||||
@Test
|
||||
public void testAll() {
|
||||
HelloServiceImpl helloServiceImpl;
|
||||
HelloService helloServiceRef;
|
||||
HelloServiceProxy helloServiceProxy;
|
||||
HelloService helloService;
|
||||
|
||||
// mock A -> B -> C
|
||||
|
||||
{ // C
|
||||
ServerConfig serverConfig1 = new ServerConfig()
|
||||
.setStopTimeout(0).setPort(22222)
|
||||
.setQueues(5).setCoreThreads(1).setMaxThreads(1);
|
||||
helloServiceImpl = new HelloServiceImpl();
|
||||
ProviderConfig<HelloService> providerConfig = new ProviderConfig<HelloService>()
|
||||
.setInterfaceId(HelloService.class.getName())
|
||||
.setRef(helloServiceImpl)
|
||||
.setServer(serverConfig1)
|
||||
.setUniqueId("x1")
|
||||
.setRegister(false);
|
||||
providerConfig.export();
|
||||
}
|
||||
{ // B
|
||||
ConsumerConfig<HelloService> consumerConfig = new ConsumerConfig<HelloService>()
|
||||
.setInterfaceId(HelloService.class.getName())
|
||||
.setTimeout(1000)
|
||||
.setDirectUrl("bolt://127.0.0.1:22222")
|
||||
.setUniqueId("x1")
|
||||
.setRegister(false);
|
||||
helloServiceRef = consumerConfig.refer();
|
||||
|
||||
ServerConfig serverConfig2 = new ServerConfig()
|
||||
.setStopTimeout(0).setPort(22223)
|
||||
.setQueues(5).setCoreThreads(1).setMaxThreads(1);
|
||||
helloServiceProxy = new HelloServiceProxy(helloServiceRef);
|
||||
ProviderConfig<HelloService> providerConfig = new ProviderConfig<HelloService>()
|
||||
.setInterfaceId(HelloService.class.getName())
|
||||
.setRef(helloServiceProxy)
|
||||
.setServer(serverConfig2)
|
||||
.setUniqueId("x2")
|
||||
.setRegister(false);
|
||||
providerConfig.export();
|
||||
}
|
||||
{ // A
|
||||
ConsumerConfig<HelloService> consumerConfig = new ConsumerConfig<HelloService>()
|
||||
.setInterfaceId(HelloService.class.getName())
|
||||
.setTimeout(1000)
|
||||
.setDirectUrl("bolt://127.0.0.1:22223")
|
||||
.setUniqueId("x2")
|
||||
.setRegister(false);
|
||||
helloService = consumerConfig.refer();
|
||||
}
|
||||
|
||||
try {
|
||||
helloService.sayHello("xxx", 22);
|
||||
// check C
|
||||
Assertions.assertNull(helloServiceImpl.getXid());
|
||||
// check B
|
||||
Assertions.assertNull(helloServiceProxy.getXid());
|
||||
} catch (Exception e) {
|
||||
Assertions.assertTrue(e instanceof SofaRpcException);
|
||||
} finally {
|
||||
Assertions.assertNull(RootContext.unbind());
|
||||
}
|
||||
|
||||
RootContext.bind("xidddd");
|
||||
try {
|
||||
helloService.sayHello("xxx", 22);
|
||||
// check C
|
||||
Assertions.assertEquals(helloServiceImpl.getXid(), "xidddd");
|
||||
// check B
|
||||
Assertions.assertEquals(helloServiceProxy.getXid(), "xidddd");
|
||||
} catch (Exception e) {
|
||||
Assertions.assertTrue(e instanceof SofaRpcException);
|
||||
} finally {
|
||||
Assertions.assertEquals("xidddd", RootContext.unbind());
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
public static void adBeforeClass() {
|
||||
RpcRunningState.setUnitTestMode(true);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void adAfterClass() {
|
||||
RpcRuntimeContext.destroy();
|
||||
RpcInternalContext.removeContext();
|
||||
RpcInvokeContext.removeContext();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user