Fix merged error
This commit is contained in:
parent
ee406d5e5e
commit
334e401f11
@ -25,6 +25,7 @@ import com.alibaba.nacos.common.http.HttpClientConfig;
|
|||||||
import com.alibaba.nacos.common.http.HttpRestResult;
|
import com.alibaba.nacos.common.http.HttpRestResult;
|
||||||
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
|
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
|
||||||
import com.alibaba.nacos.common.http.param.Header;
|
import com.alibaba.nacos.common.http.param.Header;
|
||||||
|
import com.alibaba.nacos.common.http.param.Query;
|
||||||
import com.alibaba.nacos.common.utils.ExceptionUtil;
|
import com.alibaba.nacos.common.utils.ExceptionUtil;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
@ -48,6 +48,7 @@ import com.alibaba.nacos.client.utils.TemplateUtils;
|
|||||||
import com.alibaba.nacos.common.http.HttpRestResult;
|
import com.alibaba.nacos.common.http.HttpRestResult;
|
||||||
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
|
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
|
||||||
import com.alibaba.nacos.common.http.param.Header;
|
import com.alibaba.nacos.common.http.param.Header;
|
||||||
|
import com.alibaba.nacos.common.http.param.Query;
|
||||||
import com.alibaba.nacos.common.utils.HttpMethod;
|
import com.alibaba.nacos.common.utils.HttpMethod;
|
||||||
import com.alibaba.nacos.common.utils.JacksonUtils;
|
import com.alibaba.nacos.common.utils.JacksonUtils;
|
||||||
import com.alibaba.nacos.common.utils.StringUtils;
|
import com.alibaba.nacos.common.utils.StringUtils;
|
||||||
|
@ -101,7 +101,7 @@ public class AsyncNotifyService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!httpQueue.isEmpty()) {
|
if (!httpQueue.isEmpty()) {
|
||||||
ConfigExecutor.executeAsyncNotify(new AsyncTask(nacosAsyncRestTemplate, queue));
|
ConfigExecutor.executeAsyncNotify(new AsyncTask(nacosAsyncRestTemplate, httpQueue));
|
||||||
}
|
}
|
||||||
if (!rpcQueue.isEmpty()) {
|
if (!rpcQueue.isEmpty()) {
|
||||||
ConfigExecutor.executeAsyncNotify(new AsyncRpcTask(rpcQueue));
|
ConfigExecutor.executeAsyncNotify(new AsyncRpcTask(rpcQueue));
|
||||||
|
@ -1,369 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 1999-2020 Alibaba Group Holding Ltd.
|
|
||||||
*
|
|
||||||
* 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 com.alibaba.nacos.naming.cluster.remote.grpc;
|
|
||||||
|
|
||||||
import com.alibaba.nacos.api.exception.NacosException;
|
|
||||||
import com.alibaba.nacos.api.grpc.auto.Metadata;
|
|
||||||
import com.alibaba.nacos.api.grpc.auto.Payload;
|
|
||||||
import com.alibaba.nacos.api.grpc.auto.RequestGrpc;
|
|
||||||
import com.alibaba.nacos.api.grpc.auto.RequestStreamGrpc;
|
|
||||||
import com.alibaba.nacos.api.remote.PayloadRegistry;
|
|
||||||
import com.alibaba.nacos.api.remote.request.HeartBeatRequest;
|
|
||||||
import com.alibaba.nacos.api.remote.request.Request;
|
|
||||||
import com.alibaba.nacos.api.remote.request.ServerCheckRequest;
|
|
||||||
import com.alibaba.nacos.api.remote.response.ConnectionUnregisterResponse;
|
|
||||||
import com.alibaba.nacos.api.remote.response.PlainBodyResponse;
|
|
||||||
import com.alibaba.nacos.api.remote.response.Response;
|
|
||||||
import com.alibaba.nacos.common.utils.JacksonUtils;
|
|
||||||
import com.alibaba.nacos.naming.cluster.remote.RpcClient;
|
|
||||||
import com.alibaba.nacos.naming.cluster.remote.RpcClientStatus;
|
|
||||||
import com.alibaba.nacos.naming.misc.NetUtils;
|
|
||||||
import com.alibaba.nacos.naming.misc.UtilsAndCommons;
|
|
||||||
import com.google.protobuf.Any;
|
|
||||||
import com.google.protobuf.ByteString;
|
|
||||||
import io.grpc.ManagedChannel;
|
|
||||||
import io.grpc.ManagedChannelBuilder;
|
|
||||||
import io.grpc.Status;
|
|
||||||
import io.grpc.StatusRuntimeException;
|
|
||||||
import io.grpc.stub.StreamObserver;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* gRPC Client.
|
|
||||||
*
|
|
||||||
* @author liuzunfei
|
|
||||||
* @version $Id: GrpcClient.java, v 0.1 2020年07月13日 9:16 PM liuzunfei Exp $
|
|
||||||
*/
|
|
||||||
public class GrpcClient extends RpcClient {
|
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(GrpcClient.class);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* change listeners handler registry.
|
|
||||||
*/
|
|
||||||
protected List<ServerPushResponseHandler> serverPushResponseListeners = new ArrayList<ServerPushResponseHandler>();
|
|
||||||
|
|
||||||
protected ManagedChannel channel;
|
|
||||||
|
|
||||||
protected RequestStreamGrpc.RequestStreamStub grpcStreamServiceStub;
|
|
||||||
|
|
||||||
protected RequestGrpc.RequestBlockingStub grpcServiceStub;
|
|
||||||
|
|
||||||
public GrpcClient(String target) {
|
|
||||||
super(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* create a new channel .
|
|
||||||
*
|
|
||||||
* @param serverIp serverIp.
|
|
||||||
* @param serverPort serverPort.
|
|
||||||
* @return if server check success,return stub.
|
|
||||||
*/
|
|
||||||
private RequestGrpc.RequestBlockingStub createNewChannelStub(String serverIp, int serverPort) {
|
|
||||||
|
|
||||||
ManagedChannel managedChannelTemp = ManagedChannelBuilder.forAddress(serverIp, serverPort).usePlaintext()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
RequestGrpc.RequestBlockingStub grpcServiceStubTemp = RequestGrpc.newBlockingStub(managedChannelTemp);
|
|
||||||
boolean checkSuccess = serverCheck(grpcServiceStubTemp);
|
|
||||||
LOGGER.info(String.format("create cluster channel to %s:%d result %s", serverIp, serverPort, checkSuccess));
|
|
||||||
|
|
||||||
if (checkSuccess) {
|
|
||||||
return grpcServiceStubTemp;
|
|
||||||
} else {
|
|
||||||
shuntDownChannel(managedChannelTemp);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* shutdown a channel.
|
|
||||||
*
|
|
||||||
* @param managedChannel channel to be shutdown.
|
|
||||||
*/
|
|
||||||
private void shuntDownChannel(ManagedChannel managedChannel) {
|
|
||||||
if (managedChannel != null && !managedChannel.isShutdown()) {
|
|
||||||
managedChannel.shutdownNow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void connectToServer() {
|
|
||||||
rpcClientStatus.compareAndSet(RpcClientStatus.INITED, RpcClientStatus.STARTING);
|
|
||||||
GrpcServerInfo serverInfo = resolveServerInfo(target);
|
|
||||||
RequestGrpc.RequestBlockingStub newChannelStubTemp = createNewChannelStub(serverInfo.serverIp,
|
|
||||||
serverInfo.serverPort);
|
|
||||||
if (newChannelStubTemp != null) {
|
|
||||||
RequestStreamGrpc.RequestStreamStub requestStreamStubTemp = RequestStreamGrpc
|
|
||||||
.newStub(newChannelStubTemp.getChannel());
|
|
||||||
bindRequestStream(requestStreamStubTemp);
|
|
||||||
//switch current channel and stub
|
|
||||||
channel = (ManagedChannel) newChannelStubTemp.getChannel();
|
|
||||||
grpcStreamServiceStub = requestStreamStubTemp;
|
|
||||||
grpcServiceStub = newChannelStubTemp;
|
|
||||||
rpcClientStatus.set(RpcClientStatus.RUNNING);
|
|
||||||
} else {
|
|
||||||
switchServer(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() throws NacosException {
|
|
||||||
|
|
||||||
if (rpcClientStatus.get() == RpcClientStatus.WAIT_INIT) {
|
|
||||||
LOGGER.error("RpcClient has not init yet, please check init ServerListFactory...");
|
|
||||||
throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "RpcClient not init yet");
|
|
||||||
}
|
|
||||||
if (rpcClientStatus.get() == RpcClientStatus.RUNNING || rpcClientStatus.get() == RpcClientStatus.STARTING) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
connectToServer();
|
|
||||||
|
|
||||||
executorService.scheduleWithFixedDelay(() -> sendBeat(), 0, 3000, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* switch a new server.
|
|
||||||
*/
|
|
||||||
private void switchServer(final boolean onStarting) {
|
|
||||||
|
|
||||||
if (onStarting) {
|
|
||||||
// access on startup fail
|
|
||||||
rpcClientStatus.set(RpcClientStatus.SWITCHING_SERVER);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// access from running status, sendbeat fail or receive reset message from server.
|
|
||||||
boolean changeStatusSuccess = rpcClientStatus
|
|
||||||
.compareAndSet(RpcClientStatus.RUNNING, RpcClientStatus.SWITCHING_SERVER);
|
|
||||||
if (!changeStatusSuccess) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
executorService.schedule(() -> {
|
|
||||||
// loop until start client success.
|
|
||||||
while (!isRunning()) {
|
|
||||||
|
|
||||||
//1.get a new server
|
|
||||||
GrpcServerInfo serverInfo = resolveServerInfo(target);
|
|
||||||
|
|
||||||
//2.get a new channel to new server
|
|
||||||
RequestGrpc.RequestBlockingStub newChannelStubTemp = createNewChannelStub(serverInfo.serverIp,
|
|
||||||
serverInfo.serverPort);
|
|
||||||
if (newChannelStubTemp != null) {
|
|
||||||
RequestStreamGrpc.RequestStreamStub requestStreamStubTemp = RequestStreamGrpc
|
|
||||||
.newStub(newChannelStubTemp.getChannel());
|
|
||||||
bindRequestStream(requestStreamStubTemp);
|
|
||||||
final ManagedChannel depratedChannel = channel;
|
|
||||||
//switch current channel and stub
|
|
||||||
channel = (ManagedChannel) newChannelStubTemp.getChannel();
|
|
||||||
grpcStreamServiceStub = requestStreamStubTemp;
|
|
||||||
grpcServiceStub = newChannelStubTemp;
|
|
||||||
rpcClientStatus.getAndSet(RpcClientStatus.RUNNING);
|
|
||||||
shuntDownChannel(depratedChannel);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
//sleep 3 second to switch next server.
|
|
||||||
Thread.sleep(3000L);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 0L, TimeUnit.MILLISECONDS);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send Heart Beat Request.
|
|
||||||
*/
|
|
||||||
public void sendBeat() {
|
|
||||||
try {
|
|
||||||
|
|
||||||
if (!isRunning()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
HeartBeatRequest heartBeatRequest = new HeartBeatRequest();
|
|
||||||
Response response = request(heartBeatRequest);
|
|
||||||
if (response instanceof ConnectionUnregisterResponse) {
|
|
||||||
LOGGER.warn("Send heart beat fail,connection is not registerd,trying to switch server ");
|
|
||||||
switchServer(false);
|
|
||||||
}
|
|
||||||
} catch (StatusRuntimeException e) {
|
|
||||||
if (Status.UNAVAILABLE.getCode().equals(e.getStatus().getCode())) {
|
|
||||||
LOGGER.warn("Send heart beat fail,server is not avaliable now,trying to switch server ");
|
|
||||||
switchServer(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOGGER.error("Send heart beat error, ", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Metadata.Builder buildMeta() {
|
|
||||||
return Metadata.newBuilder().setClientIp(NetUtils.localServer())
|
|
||||||
.setClientVersion(UtilsAndCommons.SERVER_VERSION);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* chenck server if ok.
|
|
||||||
*
|
|
||||||
* @param requestBlockingStub requestBlockingStub used to check server.
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private boolean serverCheck(RequestGrpc.RequestBlockingStub requestBlockingStub) {
|
|
||||||
try {
|
|
||||||
ServerCheckRequest serverCheckRequest = new ServerCheckRequest();
|
|
||||||
|
|
||||||
Metadata meta = Metadata.newBuilder().setClientIp(NetUtils.localServer())
|
|
||||||
.setClientVersion(UtilsAndCommons.SERVER_VERSION).setType(ServerCheckRequest.class.getName())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
Payload streamRequest = Payload.newBuilder().setMetadata(meta).setBody(
|
|
||||||
Any.newBuilder().setValue(ByteString.copyFromUtf8(JacksonUtils.toJson(serverCheckRequest)))
|
|
||||||
.build()).build();
|
|
||||||
Payload response = requestBlockingStub.request(streamRequest);
|
|
||||||
return response != null;
|
|
||||||
} catch (Exception e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* bind request stream observer (send a connection).
|
|
||||||
*
|
|
||||||
* @param streamStub streamStub to bind.
|
|
||||||
*/
|
|
||||||
private void bindRequestStream(RequestStreamGrpc.RequestStreamStub streamStub) {
|
|
||||||
Payload streamRequest = Payload.newBuilder().setMetadata(buildMeta()).build();
|
|
||||||
LOGGER.info("GrpcClient send stream request grpc server,streamRequest:{}", streamRequest);
|
|
||||||
streamStub.requestStream(streamRequest, new StreamObserver<Payload>() {
|
|
||||||
@Override
|
|
||||||
public void onNext(Payload grpcResponse) {
|
|
||||||
|
|
||||||
LOGGER.debug(" stream response receive ,original reponse :{}", grpcResponse);
|
|
||||||
try {
|
|
||||||
|
|
||||||
String message = grpcResponse.getBody().getValue().toStringUtf8();
|
|
||||||
String type = grpcResponse.getMetadata().getType();
|
|
||||||
String bodyString = grpcResponse.getBody().getValue().toStringUtf8();
|
|
||||||
Class classByType = PayloadRegistry.getClassbyType(type);
|
|
||||||
final Response response;
|
|
||||||
if (classByType != null) {
|
|
||||||
response = (Response) JacksonUtils.toObj(bodyString, classByType);
|
|
||||||
} else {
|
|
||||||
PlainBodyResponse myresponse = JacksonUtils.toObj(bodyString, PlainBodyResponse.class);
|
|
||||||
myresponse.setBodyString(bodyString);
|
|
||||||
response = myresponse;
|
|
||||||
}
|
|
||||||
serverPushResponseListeners
|
|
||||||
.forEach(serverPushResponseHandler -> serverPushResponseHandler.responseReply(response));
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOGGER.error("error tp process server push response :{}", grpcResponse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Throwable throwable) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCompleted() {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response request(Request request) throws NacosException {
|
|
||||||
|
|
||||||
if (!this.isRunning()) {
|
|
||||||
throw new IllegalStateException("Client is not connected to any server now,please retry later");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
|
|
||||||
Payload grpcrequest = Payload.newBuilder().setMetadata(buildMeta().setType(request.getClass().getName()))
|
|
||||||
.setBody(Any.newBuilder().setValue(ByteString.copyFromUtf8(JacksonUtils.toJson(request)))).build();
|
|
||||||
Payload response = grpcServiceStub.request(grpcrequest);
|
|
||||||
String type = response.getMetadata().getType();
|
|
||||||
String bodyString = response.getBody().getValue().toStringUtf8();
|
|
||||||
|
|
||||||
// transfrom grpcResponse to response model
|
|
||||||
Class classByType = PayloadRegistry.getClassbyType(type);
|
|
||||||
if (classByType != null) {
|
|
||||||
Object object = JacksonUtils.toObj(bodyString, classByType);
|
|
||||||
if (object instanceof ConnectionUnregisterResponse) {
|
|
||||||
switchServer(false);
|
|
||||||
throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "connection is not connected.");
|
|
||||||
}
|
|
||||||
return (Response) object;
|
|
||||||
} else {
|
|
||||||
PlainBodyResponse myresponse = JacksonUtils.toObj(bodyString, PlainBodyResponse.class);
|
|
||||||
myresponse.setBodyString(bodyString);
|
|
||||||
return (PlainBodyResponse) myresponse;
|
|
||||||
}
|
|
||||||
} catch (StatusRuntimeException e) {
|
|
||||||
if (Status.UNAVAILABLE.equals(e.getStatus())) {
|
|
||||||
LOGGER.warn("request fail,server is not avaliable now,trying to switch server ");
|
|
||||||
switchServer(false);
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOGGER.error("grpc client request error, error message is ", e.getMessage(), e);
|
|
||||||
throw new NacosException(NacosException.SERVER_ERROR, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void shutdown() throws NacosException {
|
|
||||||
if (this.channel != null && !this.channel.isShutdown()) {
|
|
||||||
this.channel.shutdownNow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private GrpcServerInfo resolveServerInfo(String serverAddress) {
|
|
||||||
GrpcServerInfo serverInfo = new GrpcServerInfo();
|
|
||||||
serverInfo.serverPort = 1000;
|
|
||||||
if (serverAddress.contains("http")) {
|
|
||||||
serverInfo.serverIp = serverAddress.split(":")[1].replaceAll("//", "");
|
|
||||||
serverInfo.serverPort += Integer.valueOf(serverAddress.split(":")[2].replaceAll("//", ""));
|
|
||||||
} else {
|
|
||||||
serverInfo.serverIp = serverAddress.split(":")[0];
|
|
||||||
serverInfo.serverPort += Integer.valueOf(serverAddress.split(":")[1]);
|
|
||||||
}
|
|
||||||
return serverInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
class GrpcServerInfo {
|
|
||||||
|
|
||||||
String serverIp;
|
|
||||||
|
|
||||||
int serverPort;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -19,6 +19,7 @@ package com.alibaba.nacos.naming.cluster.remote.grpc;
|
|||||||
import com.alibaba.nacos.api.exception.NacosException;
|
import com.alibaba.nacos.api.exception.NacosException;
|
||||||
import com.alibaba.nacos.api.remote.request.Request;
|
import com.alibaba.nacos.api.remote.request.Request;
|
||||||
import com.alibaba.nacos.api.remote.response.Response;
|
import com.alibaba.nacos.api.remote.response.Response;
|
||||||
|
import com.alibaba.nacos.common.remote.client.RpcClientFactory;
|
||||||
import com.alibaba.nacos.common.remote.client.grpc.GrpcClient;
|
import com.alibaba.nacos.common.remote.client.grpc.GrpcClient;
|
||||||
import com.alibaba.nacos.naming.cluster.remote.ClusterClient;
|
import com.alibaba.nacos.naming.cluster.remote.ClusterClient;
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ public class GrpcClusterClient implements ClusterClient {
|
|||||||
private final GrpcClient grpcClient;
|
private final GrpcClient grpcClient;
|
||||||
|
|
||||||
public GrpcClusterClient(String targetAddress) {
|
public GrpcClusterClient(String targetAddress) {
|
||||||
this.grpcClient = new GrpcClient(new SingleServerListFactory(targetAddress));
|
this.grpcClient = new GrpcClient(targetAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -18,6 +18,8 @@ package com.alibaba.nacos.naming.cluster.remote.grpc;
|
|||||||
|
|
||||||
import com.alibaba.nacos.common.remote.client.ServerListFactory;
|
import com.alibaba.nacos.common.remote.client.ServerListFactory;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Single server list factory.
|
* Single server list factory.
|
||||||
*
|
*
|
||||||
@ -40,4 +42,9 @@ public class SingleServerListFactory implements ServerListFactory {
|
|||||||
public String getCurrentServer() {
|
public String getCurrentServer() {
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getServerList() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user