diff --git a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/client/SentinelApiClient.java b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/client/SentinelApiClient.java index 008e67c1..396258e3 100755 --- a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/client/SentinelApiClient.java +++ b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/client/SentinelApiClient.java @@ -69,6 +69,7 @@ import org.apache.http.client.entity.UrlEncodedFormEntity; 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.client.utils.URLEncodedUtils; import org.apache.http.concurrent.FutureCallback; import org.apache.http.entity.ContentType; import org.apache.http.impl.client.DefaultRedirectStrategy; @@ -93,6 +94,8 @@ public class SentinelApiClient { private static Logger logger = LoggerFactory.getLogger(SentinelApiClient.class); private static final Charset DEFAULT_CHARSET = Charset.forName(SentinelConfig.charset()); + private static final String HTTP_HEADER_CONTENT_TYPE = "Content-Type"; + private static final String HTTP_HEADER_CONTENT_TYPE_URLENCODED = ContentType.create(URLEncodedUtils.CONTENT_TYPE).toString(); private static final String RESOURCE_URL_PATH = "jsonTree"; private static final String CLUSTER_NODE_PATH = "clusterNode"; @@ -106,7 +109,6 @@ public class SentinelApiClient { private static final String FETCH_CLUSTER_CLIENT_CONFIG_PATH = "cluster/client/fetchConfig"; private static final String MODIFY_CLUSTER_CLIENT_CONFIG_PATH = "cluster/client/modifyConfig"; - private static final String FETCH_CLUSTER_SERVER_ALL_CONFIG_PATH = "cluster/server/fetchConfig"; private static final String FETCH_CLUSTER_SERVER_BASIC_INFO_PATH = "cluster/server/info"; private static final String MODIFY_CLUSTER_SERVER_TRANSPORT_CONFIG_PATH = "cluster/server/modifyTransportConfig"; @@ -127,6 +129,7 @@ public class SentinelApiClient { private CloseableHttpAsyncClient httpClient; private static final SentinelVersion version160 = new SentinelVersion(1, 6, 0); + private static final SentinelVersion version171 = new SentinelVersion(1, 7, 1); @Autowired private AppManagement appManagement; @@ -151,6 +154,30 @@ public class SentinelApiClient { return statusCode == 400 && StringUtil.isNotEmpty(body) && body.contains(CommandConstants.MSG_UNKNOWN_COMMAND_PREFIX); } + protected boolean isSupportPost(String app, String ip, int port) { + return StringUtil.isNotEmpty(app) && Optional.ofNullable(appManagement.getDetailApp(app)) + .flatMap(e -> e.getMachine(ip, port)) + .flatMap(m -> VersionUtils.parseVersion(m.getVersion()) + .map(v -> v.greaterOrEqual(version160))) + .orElse(false); + } + + /** + * Check wheter target instance (identified by tuple of app-ip:port) + * supports the form of "xxxxx; xx=xx" in "Content-Type" header. + * + * @param app target app name + * @param ip target node's address + * @param port target node's port + */ + protected boolean isSupportEnhancedContentType(String app, String ip, int port) { + return StringUtil.isNotEmpty(app) && Optional.ofNullable(appManagement.getDetailApp(app)) + .flatMap(e -> e.getMachine(ip, port)) + .flatMap(m -> VersionUtils.parseVersion(m.getVersion()) + .map(v -> v.greaterOrEqual(version171))) + .orElse(false); + } + private StringBuilder queryString(Map params) { StringBuilder queryStringBuilder = new StringBuilder(); for (Entry entry : params.entrySet()) { @@ -169,7 +196,15 @@ public class SentinelApiClient { return queryStringBuilder; } - private HttpUriRequest postRequest(String url, Map params) { + /** + * Build an `HttpUriRequest` in POST way. + * + * @param url + * @param params + * @param supportEnhancedContentType see {@link #isSupportEnhancedContentType(String, String, int)} + * @return + */ + protected static HttpUriRequest postRequest(String url, Map params, boolean supportEnhancedContentType) { HttpPost httpPost = new HttpPost(url); if (params != null && params.size() > 0) { List list = new ArrayList<>(params.size()); @@ -177,6 +212,9 @@ public class SentinelApiClient { list.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); } httpPost.setEntity(new UrlEncodedFormEntity(list, Consts.UTF_8)); + if (!supportEnhancedContentType) { + httpPost.setHeader(HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_CONTENT_TYPE_URLENCODED); + } } return httpPost; } @@ -193,7 +231,7 @@ public class SentinelApiClient { private String getBody(HttpResponse response) throws Exception { Charset charset = null; try { - String contentTypeStr = response.getFirstHeader("Content-type").getValue(); + String contentTypeStr = response.getFirstHeader(HTTP_HEADER_CONTENT_TYPE).getValue(); if (StringUtil.isNotEmpty(contentTypeStr)) { ContentType contentType = ContentType.parse(contentTypeStr); charset = contentType.getCharset(); @@ -250,12 +288,7 @@ public class SentinelApiClient { if (params == null) { params = Collections.emptyMap(); } - boolean supportPost = StringUtil.isNotEmpty(app) && Optional.ofNullable(appManagement.getDetailApp(app)) - .flatMap(e -> e.getMachine(ip, port)) - .flatMap(m -> VersionUtils.parseVersion(m.getVersion()) - .map(v -> v.greaterOrEqual(version160))) - .orElse(false); - if (!useHttpPost || !supportPost) { + if (!useHttpPost || !isSupportPost(app, ip, port)) { // Using GET in older versions, append parameters after url if (!params.isEmpty()) { if (urlBuilder.indexOf("?") == -1) { @@ -268,7 +301,8 @@ public class SentinelApiClient { return executeCommand(new HttpGet(urlBuilder.toString())); } else { // Using POST - return executeCommand(postRequest(urlBuilder.toString(), params)); + return executeCommand( + postRequest(urlBuilder.toString(), params, isSupportEnhancedContentType(app, ip, port))); } } diff --git a/sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/client/SentinelApiClientTest.java b/sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/client/SentinelApiClientTest.java new file mode 100644 index 00000000..08e35e63 --- /dev/null +++ b/sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/client/SentinelApiClientTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2018 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.csp.sentinel.dashboard.client; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.apache.http.HttpException; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.protocol.RequestContent; +import org.junit.Test; + +public class SentinelApiClientTest { + @Test + public void postRequest() throws HttpException, IOException { + // Processor is required because it will determine the final request body including + // headers before outgoing. + RequestContent processor = new RequestContent(); + Map params = new HashMap(); + params.put("a", "1"); + params.put("b", "2+"); + params.put("c", "3 "); + + HttpUriRequest request; + + request = SentinelApiClient.postRequest("/test", params, false); + assertNotNull(request); + processor.process(request, null); + assertNotNull(request.getFirstHeader("Content-Type")); + assertEquals("application/x-www-form-urlencoded", request.getFirstHeader("Content-Type").getValue()); + + request = SentinelApiClient.postRequest("/test", params, true); + assertNotNull(request); + processor.process(request, null); + assertNotNull(request.getFirstHeader("Content-Type")); + assertEquals("application/x-www-form-urlencoded; charset=UTF-8", request.getFirstHeader("Content-Type").getValue()); + } +}