package me.hao0.antares.client.core;
import com.alibaba.fastjson.JSON;
import com.github.kevinsawicki.http.HttpRequest;
import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import me.hao0.antares.client.exception.AuthFailException;
import me.hao0.antares.client.exception.Server503Exception;
import me.hao0.antares.client.util.MapUtil;
import me.hao0.antares.common.balance.LoadBalance;
import me.hao0.antares.common.balance.RandomLoadBalance;
import me.hao0.antares.common.dto.*;
import me.hao0.antares.common.http.Http;
import me.hao0.antares.common.http.HttpMethod;
import me.hao0.antares.common.model.enums.ShardOperateRespCode;
import me.hao0.antares.common.support.Lifecycle;
import me.hao0.antares.common.support.Component;
import me.hao0.antares.common.util.ClientUris;
import static me.hao0.antares.common.util.Constants.*;
import me.hao0.antares.common.util.Langs;
import me.hao0.antares.common.util.Sleeps;
import me.hao0.antares.common.util.Systems;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;
/**
* Author: haolin
* Email: haolin.h0@gmail.com
*/
public class AntaresHttpAgent extends Component implements Lifecycle {
private static final Logger log = LoggerFactory.getLogger(AntaresHttpAgent.class);
private final AbstractAntaresClient client;
/**
* The current using server
*/
private volatile String currentServer;
/**
* The request headers
*/
private final Map<String, String> headers = Maps.newHashMap();
private final LoadBalance<String> balancer = new RandomLoadBalance<>();
public AntaresHttpAgent(AbstractAntaresClient client){
this.client = client;
}
public void setCurrentServer(String server) {
this.currentServer = server;
}
@Override
public void doStart() {
// select a server
selectServer();
// init header
initHttpHeaders();
}
private void initHttpHeaders() {
headers.put(APP_NAME_HEADER, client.getAppName());
headers.put(APP_KEY_HEADER, client.getAppSecret());
headers.put(CLIENT_LANG_HEADER, Langs.JAVA.ordinal() + "");
headers.put(CLIENT_VERSION_HEADER, client.getClientVersion());
}
@Override
public void doShutdown() {
}
private Boolean selectServer() {
List<String> servers = client.getHttpServers();
if (servers.isEmpty()){
log.warn("There are no available server, please check the environment.");
return Boolean.FALSE;
}
currentServer = balancer.balance(servers);
return Boolean.TRUE;
}
<T> T doGet(String uri, Map<String, String> headers, Map<String, Object> params, Class<T> targetType){
for (;;){
try {
return doGet(currentServer, uri, headers, params, targetType);
} catch (AuthFailException e){
throw new IllegalArgumentException("appKey isn't right, please check");
} catch (Server503Exception | HttpRequest.HttpRequestException e){
Sleeps.sleep(2);
String oldServer = currentServer;
log.warn("current server({}) is unavailable, try to select a new server", oldServer);
if(selectServer()){
log.info("old server({}) is unavailable, routed a select server({})", oldServer, currentServer);
} else {
log.warn("failed to select a new server, please check the server cluster");
Sleeps.sleep(5);
}
}
}
}
private <T> T doGet(String server, String uri, Map<String, String> headers, Map<String, Object> params, Class<T> targetType){
Map<String, Object> respMap = doRequest(server, uri, HttpMethod.GET, headers, params, 0);
if (respMap == null || respMap.isEmpty()){
return null;
}
return MapUtil.fromMap(respMap, targetType);
}
<T> T doPost(String uri, Map<String, String> headers, Map<String, Object> params, int readTimeout, Class<T> targetType){
for (;;){
try {
return doPost(currentServer, uri, headers, params, readTimeout, targetType);
} catch (AuthFailException e){
throw new IllegalArgumentException("appKey isn't right, please check");
} catch (Server503Exception | HttpRequest.HttpRequestException e){
String oldServer = currentServer;
log.warn("current server({}) is maybe unavailable, trying to selects a new server", oldServer);
selectServer();
log.warn("old server({}) is unavailable, select a new server({})", oldServer, currentServer);
}
}
}
private <T> T doPost(String server, String uri, Map<String, String> headers, Map<String, Object> params, int readTimeout, Class<T> targetType){
Map<String, Object> respMap = doPostAsMap(server, uri, headers, params, readTimeout);
if (respMap == null || respMap.isEmpty()){
return null;
}
return MapUtil.fromMap(respMap, targetType);
}
private Map<String, Object> doPostAsMap(String uri, Map<String, String> headers, Map<String, Object> params, int readTimeout){
return doPostAsMap(currentServer, uri, headers, params, readTimeout);
}
private Map<String, Object> doPostAsMap(String server, String uri, Map<String, String> headers, Map<String, Object> params, int readTimeout){
for (;;){
try {
Map<String, Object> respMap = doRequest(server, uri, HttpMethod.POST, headers, params, readTimeout);
if (respMap == null || respMap.isEmpty()){
return null;
}
return respMap;
} catch (AuthFailException e){
throw new IllegalArgumentException("appKey isn't right, please check");
} catch (Server503Exception | HttpRequest.HttpRequestException e){
// there are no servers available
Sleeps.sleep(3);
String oldServer = currentServer;
log.warn("current server({}) is maybe unavailable, trying to selects a new server", currentServer);
selectServer();
log.warn("old server({}) is unavailable, select a new server({})", oldServer, currentServer);
server = currentServer;
}
}
}
@SuppressWarnings("unchecked")
private Map<String, Object> doRequest(String server, String uri, HttpMethod method,
Map<String, String> headers, Map<String, Object> params, int readTimeout){
String reqUri = HTTP_PREFIX + server + ClientUris.CLIENTS + uri;
Http http;
if (method == HttpMethod.GET){
http = Http.get(reqUri);
} else {
http = Http.post(reqUri);
}
if (readTimeout > 0){
http.readTimeout(readTimeout);
}
if (headers != null){
http.headers(headers);
}
String resp = http.params(params).request();
if (Strings.isNullOrEmpty(resp)){
return null;
}
return checkRespErr(resp);
}
@SuppressWarnings("unchecked")
private Map<String, Object> checkRespErr(String respJson) {
Map<String, Object> resp = JSON.parseObject(respJson, Map.class);
Integer status = getRespStatus(resp);
if (Objects.equal(JsonResponse.SERVER_ERR.getStatus(), status)){
throw new Server503Exception();
} else if (Objects.equal(JsonResponse.AUTH_FAIL.getStatus(), status)){
throw new AuthFailException();
}
return resp;
}
/**
* Pull the job instance's shard
* @param jobInstanceId the job instance id
* @return the pull shard resp
*/
public ShardPullResp pullJobInstanceShard(Long jobInstanceId) {
Map<String, Object> params = Maps.newHashMapWithExpectedSize(2);
params.put("client", Systems.hostPid());
params.put("instanceId", jobInstanceId);
Map<String, Object> respMap = doPostAsMap(ClientUris.SHARD_PULL, headers, params, 0);
if (respMap == null){
return null;
}
Integer status = getRespStatus(respMap);
if (Objects.equal(JsonResponse.OK, status)){
// ok
PullShard shard = MapUtil.fromMap((Map<?, ?>)respMap.get("data"), PullShard.class);
return new ShardPullResp(null, shard);
}
// business error
Integer code = getErrCode(respMap);
return new ShardPullResp(ShardOperateRespCode.from(code), null);
}
/**
* Return the job instance's shard
* @param jobInstanceId the job instance id
* @param shardId the shard id
* @return the finish shard resp
*/
public ShardOperateResp returnJobInstanceShard(Long jobInstanceId, Long shardId) {
Map<String, Object> params = Maps.newHashMapWithExpectedSize(3);
params.put("instanceId", jobInstanceId);
params.put("shardId", shardId);
params.put("client", Systems.hostPid());
Map<String, Object> respMap = doPostAsMap(ClientUris.SHARD_RETURN, headers, params, 0);
if (respMap == null){
return null;
}
Integer status = getRespStatus(respMap);
if (Objects.equal(JsonResponse.OK, status)){
// ok
Boolean success = Boolean.valueOf(respMap.get("data").toString());
return new ShardOperateResp(null, success);
}
// business error
Integer code = getErrCode(respMap);
return new ShardOperateResp(ShardOperateRespCode.from(code), null);
}
/**
* Finish the job instance's shard
* @param shardFinishDto the job instance id
* @return the finish shard resp
*/
public ShardOperateResp finishJobInstanceShard(ShardFinishDto shardFinishDto) {
Map<String, Object> params = MapUtil.toMap(shardFinishDto);
Map<String, Object> respMap = doPostAsMap(ClientUris.SHARD_FINISH, headers, params, 0);
if (respMap == null){
return null;
}
Integer status = getRespStatus(respMap);
if (Objects.equal(JsonResponse.OK, status)){
// ok
Boolean success = Boolean.valueOf(respMap.get("data").toString());
return new ShardOperateResp(null, success);
}
// business error
Integer code = getErrCode(respMap);
return new ShardOperateResp(ShardOperateRespCode.from(code), null);
}
protected Integer getRespStatus(Map<String, Object> respMap) {
return Integer.valueOf(respMap.get("status").toString());
}
protected Integer getErrCode(Map<String, Object> respMap) {
return Integer.valueOf(respMap.get("err").toString());
}
}