diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/RequestUtil.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/RequestUtil.java index fd566906a66..0e34c86d080 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/utils/RequestUtil.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/RequestUtil.java @@ -18,6 +18,9 @@ import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.context.RequestContextHolder; +import com.alibaba.nacos.core.utils.WebUtils; +import com.alibaba.nacos.plugin.auth.api.IdentityContext; import javax.servlet.http.HttpServletRequest; @@ -28,30 +31,24 @@ */ public class RequestUtil { - private static final String X_REAL_IP = "X-Real-IP"; - - private static final String X_FORWARDED_FOR = "X-Forwarded-For"; - - private static final String X_FORWARDED_FOR_SPLIT_SYMBOL = ","; - public static final String CLIENT_APPNAME_HEADER = "Client-AppName"; /** - * get real client ip - * - *

first use X-Forwarded-For header https://zh.wikipedia.org/wiki/X-Forwarded-For next nginx X-Real-IP last - * {@link HttpServletRequest#getRemoteAddr()} + * Get real client ip from context first, if no value, use + * {@link com.alibaba.nacos.core.utils.WebUtils#getRemoteIp(HttpServletRequest)}. * * @param request {@link HttpServletRequest} * @return remote ip address. */ public static String getRemoteIp(HttpServletRequest request) { - String xForwardedFor = request.getHeader(X_FORWARDED_FOR); - if (!StringUtils.isBlank(xForwardedFor)) { - return xForwardedFor.split(X_FORWARDED_FOR_SPLIT_SYMBOL)[0].trim(); + String remoteIp = RequestContextHolder.getContext().getBasicContext().getAddressContext().getSourceIp(); + if (StringUtils.isBlank(remoteIp)) { + remoteIp = RequestContextHolder.getContext().getBasicContext().getAddressContext().getRemoteIp(); } - String nginxHeader = request.getHeader(X_REAL_IP); - return StringUtils.isBlank(nginxHeader) ? request.getRemoteAddr() : nginxHeader; + if (StringUtils.isBlank(remoteIp)) { + remoteIp = WebUtils.getRemoteIp(request); + } + return remoteIp; } /** @@ -61,7 +58,12 @@ public static String getRemoteIp(HttpServletRequest request) { * @return may be return null */ public static String getAppName(HttpServletRequest request) { - return request.getHeader(CLIENT_APPNAME_HEADER); + String result = RequestContextHolder.getContext().getBasicContext().getApp(); + return isUnknownApp(result) ? request.getHeader(CLIENT_APPNAME_HEADER) : result; + } + + private static boolean isUnknownApp(String appName) { + return StringUtils.isBlank(appName) || StringUtils.equalsIgnoreCase("unknown", appName); } /** @@ -71,8 +73,12 @@ public static String getAppName(HttpServletRequest request) { * @return may be return null */ public static String getSrcUserName(HttpServletRequest request) { - String result = (String) request.getSession() - .getAttribute(com.alibaba.nacos.plugin.auth.constant.Constants.Identity.IDENTITY_ID); + IdentityContext identityContext = RequestContextHolder.getContext().getAuthContext().getIdentityContext(); + String result = StringUtils.EMPTY; + if (null != identityContext) { + result = (String) identityContext.getParameter( + com.alibaba.nacos.plugin.auth.constant.Constants.Identity.IDENTITY_ID); + } // If auth is disabled, get username from parameters by agreed key return StringUtils.isBlank(result) ? request.getParameter(Constants.USERNAME) : result; } diff --git a/config/src/test/java/com/alibaba/nacos/config/server/utils/RequestUtilTest.java b/config/src/test/java/com/alibaba/nacos/config/server/utils/RequestUtilTest.java index eb25966ab3f..d272e221083 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/utils/RequestUtilTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/utils/RequestUtilTest.java @@ -17,11 +17,13 @@ package com.alibaba.nacos.config.server.utils; import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.core.context.RequestContextHolder; +import com.alibaba.nacos.plugin.auth.api.IdentityContext; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.eq; @@ -32,8 +34,13 @@ class RequestUtilTest { private static final String X_FORWARDED_FOR = "X-Forwarded-For"; + @AfterEach + void tearDown() { + RequestContextHolder.removeContext(); + } + @Test - void testGetRemoteIp() { + void testGetRemoteIpFromRequest() { HttpServletRequest request = Mockito.mock(HttpServletRequest.class); Mockito.when(request.getRemoteAddr()).thenReturn("127.0.0.1"); @@ -56,29 +63,33 @@ void testGetRemoteIp() { } @Test - void testGetAppName() { + void testGetAppNameFromContext() { + RequestContextHolder.getContext().getBasicContext().setApp("contextApp"); + HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + Mockito.when(request.getHeader(eq(RequestUtil.CLIENT_APPNAME_HEADER))).thenReturn("test"); + assertEquals("contextApp", RequestUtil.getAppName(request)); + } + + @Test + void testGetAppNameFromRequest() { HttpServletRequest request = Mockito.mock(HttpServletRequest.class); Mockito.when(request.getHeader(eq(RequestUtil.CLIENT_APPNAME_HEADER))).thenReturn("test"); assertEquals("test", RequestUtil.getAppName(request)); } @Test - void testGetSrcUserNameV1() { + void testGetSrcUserNameFromContext() { + IdentityContext identityContext = new IdentityContext(); + identityContext.setParameter(com.alibaba.nacos.plugin.auth.constant.Constants.Identity.IDENTITY_ID, "test"); + RequestContextHolder.getContext().getAuthContext().setIdentityContext(identityContext); HttpServletRequest request = Mockito.mock(HttpServletRequest.class); - HttpSession session = Mockito.mock(HttpSession.class); - Mockito.when(request.getSession()).thenReturn(session); - Mockito.when(session.getAttribute(eq(com.alibaba.nacos.plugin.auth.constant.Constants.Identity.IDENTITY_ID))).thenReturn("test"); assertEquals("test", RequestUtil.getSrcUserName(request)); } @Test - void testGetSrcUserNameV2() { + void testGetSrcUserNameFromRequest() { HttpServletRequest request = Mockito.mock(HttpServletRequest.class); - HttpSession session = Mockito.mock(HttpSession.class); - Mockito.when(request.getSession()).thenReturn(session); - Mockito.when(session.getAttribute(eq(com.alibaba.nacos.plugin.auth.constant.Constants.Identity.IDENTITY_ID))).thenReturn(null); Mockito.when(request.getParameter(eq(Constants.USERNAME))).thenReturn("parameterName"); assertEquals("parameterName", RequestUtil.getSrcUserName(request)); } - } diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java b/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java index 55c00f9852e..5bf98b5f1e6 100644 --- a/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java +++ b/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java @@ -22,6 +22,8 @@ import com.alibaba.nacos.common.utils.ExceptionUtil; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.core.code.ControllerMethodsCache; +import com.alibaba.nacos.core.context.RequestContext; +import com.alibaba.nacos.core.context.RequestContextHolder; import com.alibaba.nacos.core.utils.Loggers; import com.alibaba.nacos.core.utils.WebUtils; import com.alibaba.nacos.plugin.auth.api.IdentityContext; @@ -120,11 +122,16 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha Resource resource = protocolAuthService.parseResource(req, secured); IdentityContext identityContext = protocolAuthService.parseIdentity(req); boolean result = protocolAuthService.validateIdentity(identityContext, resource); + RequestContext requestContext = RequestContextHolder.getContext(); + requestContext.getAuthContext().setIdentityContext(identityContext); + requestContext.getAuthContext().setResource(resource); + if (null == requestContext.getAuthContext().getAuthResult()) { + requestContext.getAuthContext().setAuthResult(result); + } if (!result) { // TODO Get reason of failure throw new AccessException("Validate Identity failed."); } - injectIdentityId(req, identityContext); String action = secured.action().toString(); result = protocolAuthService.validateAuthority(identityContext, new Permission(resource, action)); if (!result) { @@ -146,21 +153,4 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Server failed, " + e.getMessage()); } } - - /** - * Set identity id to request session, make sure some actual logic can get identity information. - * - *

May be replaced with whole identityContext. - * - * @param request http request - * @param identityContext identity context - */ - private void injectIdentityId(HttpServletRequest request, IdentityContext identityContext) { - String identityId = identityContext.getParameter( - com.alibaba.nacos.plugin.auth.constant.Constants.Identity.IDENTITY_ID, StringUtils.EMPTY); - request.getSession() - .setAttribute(com.alibaba.nacos.plugin.auth.constant.Constants.Identity.IDENTITY_ID, identityId); - request.getSession().setAttribute(com.alibaba.nacos.plugin.auth.constant.Constants.Identity.IDENTITY_CONTEXT, - identityContext); - } } diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilter.java b/core/src/main/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilter.java index ad9a250b01c..21b000cc9e4 100644 --- a/core/src/main/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilter.java +++ b/core/src/main/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilter.java @@ -24,6 +24,8 @@ import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.auth.config.AuthConfigs; import com.alibaba.nacos.common.utils.ExceptionUtil; +import com.alibaba.nacos.core.context.RequestContext; +import com.alibaba.nacos.core.context.RequestContextHolder; import com.alibaba.nacos.core.remote.AbstractRequestFilter; import com.alibaba.nacos.core.utils.Loggers; import com.alibaba.nacos.plugin.auth.api.IdentityContext; @@ -75,6 +77,12 @@ public Response filter(Request request, RequestMeta meta, Class handlerClazz) th Resource resource = protocolAuthService.parseResource(request, secured); IdentityContext identityContext = protocolAuthService.parseIdentity(request); boolean result = protocolAuthService.validateIdentity(identityContext, resource); + RequestContext requestContext = RequestContextHolder.getContext(); + requestContext.getAuthContext().setIdentityContext(identityContext); + requestContext.getAuthContext().setResource(resource); + if (null == requestContext.getAuthContext().getAuthResult()) { + requestContext.getAuthContext().setAuthResult(result); + } if (!result) { // TODO Get reason of failure throw new AccessException("Validate Identity failed."); diff --git a/core/src/main/java/com/alibaba/nacos/core/context/RequestContext.java b/core/src/main/java/com/alibaba/nacos/core/context/RequestContext.java new file mode 100644 index 00000000000..24b0aea49c3 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/context/RequestContext.java @@ -0,0 +1,96 @@ +/* + * Copyright 1999-2023 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.core.context; + +import com.alibaba.nacos.core.context.addition.AuthContext; +import com.alibaba.nacos.core.context.addition.BasicContext; +import com.alibaba.nacos.core.context.addition.EngineContext; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Nacos request context. + * + * @author xiweng.yy + */ +public class RequestContext { + + /** + * Optional, the request id. + *

+ */ + private String requestId; + + /** + * Request start timestamp. + */ + private final long requestTimestamp; + + private final BasicContext basicContext; + + private final EngineContext engineContext; + + private final AuthContext authContext; + + private final Map extensionContexts; + + RequestContext(long requestTimestamp) { + this.requestId = UUID.randomUUID().toString(); + this.requestTimestamp = requestTimestamp; + this.basicContext = new BasicContext(); + this.engineContext = new EngineContext(); + this.authContext = new AuthContext(); + this.extensionContexts = new HashMap<>(1); + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public String getRequestId() { + return requestId; + } + + public long getRequestTimestamp() { + return requestTimestamp; + } + + public BasicContext getBasicContext() { + return basicContext; + } + + public EngineContext getEngineContext() { + return engineContext; + } + + public AuthContext getAuthContext() { + return authContext; + } + + public Object getExtensionContext(String key) { + return extensionContexts.get(key); + } + + public void addExtensionContext(String key, Object value) { + extensionContexts.put(key, value); + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/context/RequestContextHolder.java b/core/src/main/java/com/alibaba/nacos/core/context/RequestContextHolder.java new file mode 100644 index 00000000000..316d82082d8 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/context/RequestContextHolder.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2023 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.core.context; + +import java.util.function.Supplier; + +/** + * Holder for request context for each worker thread. + * + * @author xiweng.yy + */ +public class RequestContextHolder { + + private static final Supplier REQUEST_CONTEXT_FACTORY = () -> { + long requestTimestamp = System.currentTimeMillis(); + return new RequestContext(requestTimestamp); + }; + + private static final ThreadLocal CONTEXT_HOLDER = ThreadLocal.withInitial(REQUEST_CONTEXT_FACTORY); + + public static RequestContext getContext() { + return CONTEXT_HOLDER.get(); + } + + public static void removeContext() { + CONTEXT_HOLDER.remove(); + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/context/addition/AddressContext.java b/core/src/main/java/com/alibaba/nacos/core/context/addition/AddressContext.java new file mode 100644 index 00000000000..6120a25e8c3 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/context/addition/AddressContext.java @@ -0,0 +1,90 @@ +/* + * Copyright 1999-2023 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.core.context.addition; + +/** + * Nacos request address information context. + * + * @author xiweng.yy + */ +public class AddressContext { + + /** + * Request source ip, it's the ip of the client, most situations are same with remoteIp. + */ + private String sourceIp; + + /** + * Request source port, it's the port of the client, most situations are same with remoteIp. + */ + private int sourcePort; + + /** + * Request connection ip, it should be got from the socket, which means the ip seen by nacos server. + */ + private String remoteIp; + + /** + * Request connection port, it should be got from the socket, which means the port seen by nacos server. + */ + private int remotePort; + + /** + * Request host, it's the host of the client, nullable when can't get it from request or connection. + */ + private String host; + + public String getSourceIp() { + return sourceIp; + } + + public void setSourceIp(String sourceIp) { + this.sourceIp = sourceIp; + } + + public int getSourcePort() { + return sourcePort; + } + + public void setSourcePort(int sourcePort) { + this.sourcePort = sourcePort; + } + + public String getRemoteIp() { + return remoteIp; + } + + public void setRemoteIp(String remoteIp) { + this.remoteIp = remoteIp; + } + + public int getRemotePort() { + return remotePort; + } + + public void setRemotePort(int remotePort) { + this.remotePort = remotePort; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/context/addition/AuthContext.java b/core/src/main/java/com/alibaba/nacos/core/context/addition/AuthContext.java new file mode 100644 index 00000000000..1f1ed4da70b --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/context/addition/AuthContext.java @@ -0,0 +1,63 @@ +/* + * Copyright 1999-2023 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.core.context.addition; + +import com.alibaba.nacos.plugin.auth.api.IdentityContext; +import com.alibaba.nacos.plugin.auth.api.Resource; + +/** + * Nacos auth context, store and transport some auth plugin information to handler or trace log. + * + * @author xiweng.yy + */ +public class AuthContext { + + private IdentityContext identityContext; + + private Resource resource; + + /** + * Auth result, default is {@code true} or {@code false}. + * + *

TODO with more auth result by auth plugin. + */ + private Object authResult; + + public IdentityContext getIdentityContext() { + return identityContext; + } + + public void setIdentityContext(IdentityContext identityContext) { + this.identityContext = identityContext; + } + + public Resource getResource() { + return resource; + } + + public void setResource(Resource resource) { + this.resource = resource; + } + + public Object getAuthResult() { + return authResult; + } + + public void setAuthResult(Object authResult) { + this.authResult = authResult; + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/context/addition/BasicContext.java b/core/src/main/java/com/alibaba/nacos/core/context/addition/BasicContext.java new file mode 100644 index 00000000000..4c712f83fd9 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/context/addition/BasicContext.java @@ -0,0 +1,114 @@ +/* + * Copyright 1999-2023 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.core.context.addition; + +import com.alibaba.nacos.api.common.Constants; + +/** + * Nacos request basic information context. + * + * @author xiweng.yy + */ +public class BasicContext { + + private static final String DEFAULT_APP = "unknown"; + + public static final String HTTP_PROTOCOL = "HTTP"; + + public static final String GRPC_PROTOCOL = "GRPC"; + + private final AddressContext addressContext; + + /** + * Request user agent, such as Nacos-Java-client:v2.4.0 + */ + private String userAgent; + + /** + * Request protocol type, HTTP or GRPC and so on. + */ + private String requestProtocol; + + /** + * Request target. + *

    + *
  • For HTTP protocol it should be `${Method} ${URI}`, such as `POST /v2/ns/instance`
  • + *
  • For GRPC protocol, it should be `${requestClass}`, such as `InstanceRequest`
  • + *
+ */ + private String requestTarget; + + /** + * Optional, mark the app name of the request from when client set app name, default `unknown`. + */ + private String app; + + /** + * Optional, mark the encoding of the request from when client set encoding, default `UTF-8`. + */ + private String encoding; + + public BasicContext() { + this.addressContext = new AddressContext(); + this.app = DEFAULT_APP; + this.encoding = Constants.ENCODE; + } + + public AddressContext getAddressContext() { + return addressContext; + } + + public String getUserAgent() { + return userAgent; + } + + public void setUserAgent(String userAgent) { + this.userAgent = userAgent; + } + + public String getRequestProtocol() { + return requestProtocol; + } + + public void setRequestProtocol(String requestProtocol) { + this.requestProtocol = requestProtocol; + } + + public String getRequestTarget() { + return requestTarget; + } + + public void setRequestTarget(String requestTarget) { + this.requestTarget = requestTarget; + } + + public String getApp() { + return app; + } + + public void setApp(String app) { + this.app = app; + } + + public String getEncoding() { + return encoding; + } + + public void setEncoding(String encoding) { + this.encoding = encoding; + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/context/addition/EngineContext.java b/core/src/main/java/com/alibaba/nacos/core/context/addition/EngineContext.java new file mode 100644 index 00000000000..d46f77938f5 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/context/addition/EngineContext.java @@ -0,0 +1,62 @@ +/* + * Copyright 1999-2023 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.core.context.addition; + +import com.alibaba.nacos.common.utils.VersionUtils; + +import java.util.HashMap; +import java.util.Map; + +/** + * Nacos engine context, to store some environment and engine information context. Such as version or system information. + * + * @author xiweng.yy + */ +public class EngineContext { + + /** + * Nacos server version, such as v2.4.0. + */ + private String version; + + private final Map contexts; + + public EngineContext() { + version = VersionUtils.version; + contexts = new HashMap<>(1); + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getContext(String key) { + return contexts.get(key); + } + + public String getContext(String key, String defaultValue) { + return contexts.getOrDefault(key, defaultValue); + } + + public void setContext(String key, String value) { + contexts.put(key, value); + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/context/remote/HttpRequestContextConfig.java b/core/src/main/java/com/alibaba/nacos/core/context/remote/HttpRequestContextConfig.java new file mode 100644 index 00000000000..2ffd05f71f7 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/context/remote/HttpRequestContextConfig.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2023 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.core.context.remote; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Spring Configuration for request context of HTTP. + * + * @author xiweng.yy + */ +@Configuration +public class HttpRequestContextConfig { + + @Bean + public FilterRegistrationBean requestContextFilterRegistration( + HttpRequestContextFilter requestContextFilter) { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setFilter(requestContextFilter); + registration.addUrlPatterns("/*"); + registration.setName("nacosRequestContextFilter"); + registration.setOrder(Integer.MIN_VALUE); + return registration; + } + + @Bean + public HttpRequestContextFilter nacosRequestContextFilter() { + return new HttpRequestContextFilter(); + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/context/remote/HttpRequestContextFilter.java b/core/src/main/java/com/alibaba/nacos/core/context/remote/HttpRequestContextFilter.java new file mode 100644 index 00000000000..c6b325e2b49 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/context/remote/HttpRequestContextFilter.java @@ -0,0 +1,103 @@ +/* + * Copyright 1999-2023 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.core.context.remote; + +import com.alibaba.nacos.common.constant.HttpHeaderConsts; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.context.RequestContext; +import com.alibaba.nacos.core.context.RequestContextHolder; +import com.alibaba.nacos.core.context.addition.BasicContext; +import com.alibaba.nacos.core.utils.WebUtils; +import org.apache.http.HttpHeaders; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +import static com.alibaba.nacos.api.common.Constants.CLIENT_APPNAME_HEADER; + +/** + * The Filter to add request context for HTTP protocol. + * + * @author xiweng.yy + */ +public class HttpRequestContextFilter implements Filter { + + private static final String PATTERN_REQUEST_TARGET = "%s %s"; + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + RequestContext requestContext = RequestContextHolder.getContext(); + try { + requestContext.getBasicContext().setRequestProtocol(BasicContext.HTTP_PROTOCOL); + HttpServletRequest request = (HttpServletRequest) servletRequest; + setRequestTarget(request, requestContext); + setEncoding(request, requestContext); + setAddressContext(request, requestContext); + setOtherBasicContext(request, requestContext); + filterChain.doFilter(servletRequest, servletResponse); + } finally { + RequestContextHolder.removeContext(); + } + } + + private void setRequestTarget(HttpServletRequest request, RequestContext requestContext) { + String uri = request.getRequestURI(); + String method = request.getMethod(); + requestContext.getBasicContext().setRequestTarget(String.format(PATTERN_REQUEST_TARGET, method, uri)); + } + + private void setEncoding(HttpServletRequest request, RequestContext requestContext) { + String encoding = request.getCharacterEncoding(); + if (StringUtils.isNotBlank(encoding)) { + requestContext.getBasicContext().setEncoding(encoding); + } + } + + private void setAddressContext(HttpServletRequest request, RequestContext requestContext) { + String remoteAddress = request.getRemoteAddr(); + int remotePort = request.getRemotePort(); + String sourceIp = WebUtils.getRemoteIp(request); + String host = request.getHeader(HttpHeaders.HOST); + requestContext.getBasicContext().getAddressContext().setRemoteIp(remoteAddress); + requestContext.getBasicContext().getAddressContext().setRemotePort(remotePort); + requestContext.getBasicContext().getAddressContext().setSourceIp(sourceIp); + requestContext.getBasicContext().getAddressContext().setHost(host); + } + + private void setOtherBasicContext(HttpServletRequest request, RequestContext requestContext) { + String userAgent = WebUtils.getUserAgent(request); + requestContext.getBasicContext().setUserAgent(userAgent); + String app = getAppName(request); + if (StringUtils.isNotBlank(app)) { + requestContext.getBasicContext().setApp(app); + } + } + + private String getAppName(HttpServletRequest request) { + String app = request.getHeader(HttpHeaderConsts.APP_FILED); + if (StringUtils.isBlank(app)) { + app = request.getHeader(CLIENT_APPNAME_HEADER); + } + return app; + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/paramcheck/CheckConfiguration.java b/core/src/main/java/com/alibaba/nacos/core/paramcheck/CheckConfiguration.java index efde5f6d189..259bdaada46 100644 --- a/core/src/main/java/com/alibaba/nacos/core/paramcheck/CheckConfiguration.java +++ b/core/src/main/java/com/alibaba/nacos/core/paramcheck/CheckConfiguration.java @@ -35,7 +35,7 @@ public FilterRegistrationBean checkerFilterRegistration(Para FilterRegistrationBean registration = new FilterRegistrationBean<>(); registration.setFilter(checkerFilter); registration.addUrlPatterns("/*"); - registration.setName("checkerFilter"); + registration.setName("requestContextFilter"); registration.setOrder(8); return registration; } diff --git a/core/src/main/java/com/alibaba/nacos/core/remote/ConnectionMeta.java b/core/src/main/java/com/alibaba/nacos/core/remote/ConnectionMeta.java index 51e43f65564..4f29105faa4 100644 --- a/core/src/main/java/com/alibaba/nacos/core/remote/ConnectionMeta.java +++ b/core/src/main/java/com/alibaba/nacos/core/remote/ConnectionMeta.java @@ -197,6 +197,24 @@ public void setClientIp(String clientIp) { this.clientIp = clientIp; } + /** + * Getter method for property remoteIp. + * + * @return property value of remoteIp + */ + public String getRemoteIp() { + return remoteIp; + } + + /** + * Getter method for property remotePort. + * + * @return property value of remotePort + */ + public int getRemotePort() { + return remotePort; + } + /** * Getter method for property connectionId. * diff --git a/core/src/main/java/com/alibaba/nacos/core/remote/grpc/GrpcRequestAcceptor.java b/core/src/main/java/com/alibaba/nacos/core/remote/grpc/GrpcRequestAcceptor.java index f1283c0a417..626098b967d 100644 --- a/core/src/main/java/com/alibaba/nacos/core/remote/grpc/GrpcRequestAcceptor.java +++ b/core/src/main/java/com/alibaba/nacos/core/remote/grpc/GrpcRequestAcceptor.java @@ -27,7 +27,12 @@ import com.alibaba.nacos.api.remote.response.Response; import com.alibaba.nacos.api.remote.response.ResponseCode; import com.alibaba.nacos.api.remote.response.ServerCheckResponse; +import com.alibaba.nacos.common.constant.HttpHeaderConsts; import com.alibaba.nacos.common.remote.client.grpc.GrpcUtils; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.context.RequestContext; +import com.alibaba.nacos.core.context.RequestContextHolder; +import com.alibaba.nacos.core.context.addition.BasicContext; import com.alibaba.nacos.core.monitor.MetricsMonitor; import com.alibaba.nacos.core.remote.Connection; import com.alibaba.nacos.core.remote.ConnectionManager; @@ -186,6 +191,7 @@ public void request(Payload grpcRequest, StreamObserver responseObserve requestMeta.setLabels(connection.getMetaInfo().getLabels()); requestMeta.setAbilityTable(connection.getAbilityTable()); connectionManager.refreshActiveTime(requestMeta.getConnectionId()); + prepareRequestContext(request, requestMeta, connection); Response response = requestHandler.handleRequest(request, requestMeta); Payload payloadResponse = GrpcUtils.convert(response); traceIfNecessary(payloadResponse, false); @@ -212,8 +218,26 @@ public void request(Payload grpcRequest, StreamObserver responseObserve responseObserver.onCompleted(); MetricsMonitor.recordGrpcRequestEvent(type, false, ResponseCode.FAIL.getCode(), e.getClass().getSimpleName(), request.getModule(), System.nanoTime() - startTime); + } finally { + RequestContextHolder.removeContext(); } } + private void prepareRequestContext(Request request, RequestMeta requestMeta, Connection connection) { + RequestContext requestContext = RequestContextHolder.getContext(); + requestContext.setRequestId(request.getRequestId()); + requestContext.getBasicContext().setUserAgent(requestMeta.getClientVersion()); + requestContext.getBasicContext().setRequestProtocol(BasicContext.GRPC_PROTOCOL); + requestContext.getBasicContext().setRequestTarget(request.getClass().getSimpleName()); + String app = connection.getMetaInfo().getAppName(); + if (StringUtils.isBlank(app)) { + app = request.getHeader(HttpHeaderConsts.APP_FILED, "unknown"); + } + requestContext.getBasicContext().setApp(app); + requestContext.getBasicContext().getAddressContext().setRemoteIp(connection.getMetaInfo().getRemoteIp()); + requestContext.getBasicContext().getAddressContext().setRemotePort(connection.getMetaInfo().getRemotePort()); + requestContext.getBasicContext().getAddressContext().setSourceIp(connection.getMetaInfo().getClientIp()); + } + } \ No newline at end of file diff --git a/core/src/main/java/com/alibaba/nacos/core/utils/WebUtils.java b/core/src/main/java/com/alibaba/nacos/core/utils/WebUtils.java index 1b7a43f75cb..95fa39c9cf5 100644 --- a/core/src/main/java/com/alibaba/nacos/core/utils/WebUtils.java +++ b/core/src/main/java/com/alibaba/nacos/core/utils/WebUtils.java @@ -54,6 +54,12 @@ public class WebUtils { private static final String TMP_SUFFIX = ".tmp"; + private static final String X_REAL_IP = "X-Real-IP"; + + private static final String X_FORWARDED_FOR = "X-Forwarded-For"; + + private static final String X_FORWARDED_FOR_SPLIT_SYMBOL = ","; + /** * get target value from parameterMap, if not found will throw {@link IllegalArgumentException}. * @@ -248,4 +254,22 @@ public static void process(DeferredResult deferredResult, CompletableFutu deferredResult.setResult(t); }); } + + /** + * get real client ip + * + *

first use X-Forwarded-For header https://zh.wikipedia.org/wiki/X-Forwarded-For next nginx X-Real-IP last + * {@link HttpServletRequest#getRemoteAddr()} + * + * @param request {@link HttpServletRequest} + * @return remote ip address. + */ + public static String getRemoteIp(HttpServletRequest request) { + String xForwardedFor = request.getHeader(X_FORWARDED_FOR); + if (!StringUtils.isBlank(xForwardedFor)) { + return xForwardedFor.split(X_FORWARDED_FOR_SPLIT_SYMBOL)[0].trim(); + } + String nginxHeader = request.getHeader(X_REAL_IP); + return StringUtils.isBlank(nginxHeader) ? request.getRemoteAddr() : nginxHeader; + } } diff --git a/core/src/test/java/com/alibaba/nacos/core/auth/AuthFilterTest.java b/core/src/test/java/com/alibaba/nacos/core/auth/AuthFilterTest.java index db7e3a72c40..5baa64bf268 100644 --- a/core/src/test/java/com/alibaba/nacos/core/auth/AuthFilterTest.java +++ b/core/src/test/java/com/alibaba/nacos/core/auth/AuthFilterTest.java @@ -21,7 +21,9 @@ import com.alibaba.nacos.auth.config.AuthConfigs; import com.alibaba.nacos.common.constant.HttpHeaderConsts; import com.alibaba.nacos.core.code.ControllerMethodsCache; +import com.alibaba.nacos.core.context.RequestContextHolder; import com.alibaba.nacos.sys.env.Constants; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -58,6 +60,11 @@ class AuthFilterTest { @Mock private ControllerMethodsCache methodsCache; + @AfterEach + void tearDown() { + RequestContextHolder.removeContext(); + } + @Test void testDoFilter() { try { diff --git a/core/src/test/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilterTest.java b/core/src/test/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilterTest.java index c0256f071aa..61f5963fcb2 100644 --- a/core/src/test/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilterTest.java +++ b/core/src/test/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilterTest.java @@ -24,7 +24,9 @@ import com.alibaba.nacos.api.remote.response.Response; import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.core.context.RequestContextHolder; import com.alibaba.nacos.core.remote.RequestHandler; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -50,6 +52,11 @@ class RemoteRequestAuthFilterTest { @Mock private AuthConfigs authConfigs; + @AfterEach + void tearDown() { + RequestContextHolder.removeContext(); + } + @Test void testFilter() { Mockito.when(authConfigs.isAuthEnabled()).thenReturn(true); diff --git a/core/src/test/java/com/alibaba/nacos/core/context/RequestContextHolderTest.java b/core/src/test/java/com/alibaba/nacos/core/context/RequestContextHolderTest.java new file mode 100644 index 00000000000..38074cc9402 --- /dev/null +++ b/core/src/test/java/com/alibaba/nacos/core/context/RequestContextHolderTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2023 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.core.context; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class RequestContextHolderTest { + + @AfterEach + void tearDown() { + RequestContextHolder.removeContext(); + } + + @Test + void testGetContext() { + long timestamp = System.currentTimeMillis(); + RequestContext requestContext = RequestContextHolder.getContext(); + assertNotNull(requestContext); + assertNotNull(requestContext.getRequestId()); + assertTrue(requestContext.getRequestTimestamp() >= timestamp); + assertNotNull(requestContext.getBasicContext()); + assertNotNull(requestContext.getEngineContext()); + assertNotNull(requestContext.getAuthContext()); + } +} \ No newline at end of file diff --git a/core/src/test/java/com/alibaba/nacos/core/context/RequestContextTest.java b/core/src/test/java/com/alibaba/nacos/core/context/RequestContextTest.java new file mode 100644 index 00000000000..4392f7f653c --- /dev/null +++ b/core/src/test/java/com/alibaba/nacos/core/context/RequestContextTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 1999-2023 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.core.context; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +class RequestContextTest { + + long requestTimestamp; + + RequestContext requestContext; + + @BeforeEach + void setUp() { + requestTimestamp = System.currentTimeMillis(); + requestContext = new RequestContext(requestTimestamp); + } + + @Test + public void testGetRequestId() { + String requestId = requestContext.getRequestId(); + assertNotNull(requestId); + assertNotNull(UUID.fromString(requestId)); + requestContext.setRequestId("testRequestId"); + assertEquals("testRequestId", requestContext.getRequestId()); + } + + @Test + public void testGetRequestTimestamp() { + assertEquals(requestTimestamp, requestContext.getRequestTimestamp()); + } + + @Test + public void testSetExtensionContext() { + assertNull(requestContext.getExtensionContext("testKey")); + requestContext.addExtensionContext("testKey", "testValue"); + assertEquals("testValue", requestContext.getExtensionContext("testKey")); + } +} \ No newline at end of file diff --git a/core/src/test/java/com/alibaba/nacos/core/context/addition/AddressContextTest.java b/core/src/test/java/com/alibaba/nacos/core/context/addition/AddressContextTest.java new file mode 100644 index 00000000000..ce123f3e0cd --- /dev/null +++ b/core/src/test/java/com/alibaba/nacos/core/context/addition/AddressContextTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 1999-2023 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.core.context.addition; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class AddressContextTest { + + AddressContext addressContext; + + @BeforeEach + void setUp() { + addressContext = new AddressContext(); + } + + @Test + void testSetSourceIp() { + assertNull(addressContext.getSourceIp()); + addressContext.setSourceIp("127.0.0.1"); + assertEquals("127.0.0.1", addressContext.getSourceIp()); + } + + @Test + void testSetSourcePort() { + assertEquals(0, addressContext.getSourcePort()); + addressContext.setSourcePort(8080); + assertEquals(8080, addressContext.getSourcePort()); + } + + @Test + void testSetRemoteIp() { + assertNull(addressContext.getRemoteIp()); + addressContext.setRemoteIp("127.0.0.1"); + assertEquals("127.0.0.1", addressContext.getRemoteIp()); + } + + @Test + void testSetRemotePort() { + assertEquals(0, addressContext.getRemotePort()); + addressContext.setRemotePort(8080); + assertEquals(8080, addressContext.getRemotePort()); + } + + @Test + void testSetHost() { + assertNull(addressContext.getHost()); + addressContext.setHost("127.0.0.1"); + assertEquals("127.0.0.1", addressContext.getHost()); + } +} \ No newline at end of file diff --git a/core/src/test/java/com/alibaba/nacos/core/context/addition/AuthContextTest.java b/core/src/test/java/com/alibaba/nacos/core/context/addition/AuthContextTest.java new file mode 100644 index 00000000000..972749994fb --- /dev/null +++ b/core/src/test/java/com/alibaba/nacos/core/context/addition/AuthContextTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 1999-2023 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.core.context.addition; + +import com.alibaba.nacos.plugin.auth.api.IdentityContext; +import com.alibaba.nacos.plugin.auth.api.Resource; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class AuthContextTest { + + AuthContext authContext; + + @BeforeEach + void setUp() { + authContext = new AuthContext(); + } + + @Test + void testSetIdentityContext() { + IdentityContext identityContext = new IdentityContext(); + assertNull(authContext.getIdentityContext()); + authContext.setIdentityContext(identityContext); + assertSame(identityContext, authContext.getIdentityContext()); + } + + @Test + void testSetResource() { + Resource resource = new Resource("", "", "", "", new Properties()); + assertNull(authContext.getResource()); + authContext.setResource(resource); + assertSame(resource, authContext.getResource()); + } + + @Test + void testSetAuthResult() { + assertNull(authContext.getAuthResult()); + authContext.setAuthResult(true); + assertTrue((boolean) authContext.getAuthResult()); + } +} \ No newline at end of file diff --git a/core/src/test/java/com/alibaba/nacos/core/context/addition/BasicContextTest.java b/core/src/test/java/com/alibaba/nacos/core/context/addition/BasicContextTest.java new file mode 100644 index 00000000000..90430f900e6 --- /dev/null +++ b/core/src/test/java/com/alibaba/nacos/core/context/addition/BasicContextTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 1999-2023 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.core.context.addition; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.api.naming.remote.request.InstanceRequest; +import com.alibaba.nacos.common.utils.VersionUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +class BasicContextTest { + + BasicContext basicContext; + + @BeforeEach + void setUp() { + basicContext = new BasicContext(); + } + + @Test + void testGetAddressContext() { + assertNotNull(basicContext.getAddressContext()); + } + + @Test + void testSetUserAgent() { + assertNull(basicContext.getUserAgent()); + basicContext.setUserAgent(VersionUtils.getFullClientVersion()); + assertEquals(VersionUtils.getFullClientVersion(), basicContext.getUserAgent()); + } + + @Test + void testSetRequestProtocol() { + assertNull(basicContext.getRequestProtocol()); + basicContext.setRequestProtocol(BasicContext.HTTP_PROTOCOL); + assertEquals(BasicContext.HTTP_PROTOCOL, basicContext.getRequestProtocol()); + basicContext.setRequestProtocol(BasicContext.GRPC_PROTOCOL); + assertEquals(BasicContext.GRPC_PROTOCOL, basicContext.getRequestProtocol()); + } + + @Test + void testSetRequestTarget() { + assertNull(basicContext.getRequestTarget()); + basicContext.setRequestTarget("POST /v2/ns/instance"); + assertEquals("POST /v2/ns/instance", basicContext.getRequestTarget()); + basicContext.setRequestTarget(InstanceRequest.class.getSimpleName()); + assertEquals(InstanceRequest.class.getSimpleName(), basicContext.getRequestTarget()); + } + + @Test + void testSetApp() { + assertEquals("unknown", basicContext.getApp()); + basicContext.setApp("testApp"); + assertEquals("testApp", basicContext.getApp()); + } + + @Test + void testSetEncoding() { + assertEquals(Constants.ENCODE, basicContext.getEncoding()); + basicContext.setEncoding("GBK"); + assertEquals("GBK", basicContext.getEncoding()); + } +} \ No newline at end of file diff --git a/core/src/test/java/com/alibaba/nacos/core/context/addition/EngineContextTest.java b/core/src/test/java/com/alibaba/nacos/core/context/addition/EngineContextTest.java new file mode 100644 index 00000000000..b1a1e27c820 --- /dev/null +++ b/core/src/test/java/com/alibaba/nacos/core/context/addition/EngineContextTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 1999-2023 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.core.context.addition; + +import com.alibaba.nacos.common.utils.VersionUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class EngineContextTest { + + EngineContext engineContext; + + @BeforeEach + void setUp() { + engineContext = new EngineContext(); + } + + @Test + void testSetVersion() { + assertEquals(VersionUtils.version, engineContext.getVersion()); + engineContext.setVersion("testVersion"); + assertEquals("testVersion", engineContext.getVersion()); + } + + @Test + void testSetContext() { + assertNull(engineContext.getContext("test")); + assertEquals("default", engineContext.getContext("test", "default")); + engineContext.setContext("test", "testValue"); + assertEquals("testValue", engineContext.getContext("test")); + assertEquals("testValue", engineContext.getContext("test", "default")); + } +} \ No newline at end of file diff --git a/core/src/test/java/com/alibaba/nacos/core/context/remote/HttpRequestContextConfigTest.java b/core/src/test/java/com/alibaba/nacos/core/context/remote/HttpRequestContextConfigTest.java new file mode 100644 index 00000000000..998778215a6 --- /dev/null +++ b/core/src/test/java/com/alibaba/nacos/core/context/remote/HttpRequestContextConfigTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2023 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.core.context.remote; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.web.servlet.FilterRegistrationBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class HttpRequestContextConfigTest { + + @Test + void testRequestContextFilterRegistration() { + HttpRequestContextConfig contextConfig = new HttpRequestContextConfig(); + HttpRequestContextFilter filter = contextConfig.nacosRequestContextFilter(); + FilterRegistrationBean actual = contextConfig.requestContextFilterRegistration( + filter); + assertEquals(filter, actual.getFilter()); + assertEquals("/*", actual.getUrlPatterns().iterator().next()); + assertEquals(Integer.MIN_VALUE, actual.getOrder()); + } +} \ No newline at end of file diff --git a/core/src/test/java/com/alibaba/nacos/core/context/remote/HttpRequestContextFilterTest.java b/core/src/test/java/com/alibaba/nacos/core/context/remote/HttpRequestContextFilterTest.java new file mode 100644 index 00000000000..4664fd8afd3 --- /dev/null +++ b/core/src/test/java/com/alibaba/nacos/core/context/remote/HttpRequestContextFilterTest.java @@ -0,0 +1,152 @@ +/* + * Copyright 1999-2023 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.core.context.remote; + +import com.alibaba.nacos.common.constant.HttpHeaderConsts; +import com.alibaba.nacos.core.context.RequestContext; +import com.alibaba.nacos.core.context.RequestContextHolder; +import com.alibaba.nacos.core.context.addition.BasicContext; +import org.apache.http.HttpHeaders; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.opentest4j.AssertionFailedError; +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class HttpRequestContextFilterTest { + + @Mock + private MockHttpServletRequest servletRequest; + + @Mock + private MockHttpServletResponse servletResponse; + + @Mock + private Servlet servlet; + + HttpRequestContextFilter filter; + + @BeforeEach + void setUp() { + filter = new HttpRequestContextFilter(); + RequestContextHolder.getContext(); + when(servletRequest.getHeader(HttpHeaders.HOST)).thenReturn("localhost"); + when(servletRequest.getHeader(HttpHeaders.USER_AGENT)).thenReturn("Nacos-Java-Client:v1.4.7"); + when(servletRequest.getHeader(HttpHeaderConsts.APP_FILED)).thenReturn("testApp"); + when(servletRequest.getMethod()).thenReturn("GET"); + when(servletRequest.getRequestURI()).thenReturn("/test/path"); + when(servletRequest.getCharacterEncoding()).thenReturn("GBK"); + when(servletRequest.getRemoteAddr()).thenReturn("1.1.1.1"); + when(servletRequest.getRemotePort()).thenReturn(3306); + when(servletRequest.getHeader("X-Forwarded-For")).thenReturn("2.2.2.2"); + } + + @AfterEach + void tearDown() { + RequestContextHolder.removeContext(); + } + + @Test + public void testDoFilterSetsCorrectContextValues() throws Exception { + MockNextFilter nextFilter = new MockNextFilter("testApp", "GBK"); + filter.doFilter(servletRequest, servletResponse, new MockFilterChain(servlet, nextFilter)); + if (null != nextFilter.error) { + throw nextFilter.error; + } + } + + @Test + public void testDoFilterWithoutEncoding() throws Exception { + when(servletRequest.getCharacterEncoding()).thenReturn(""); + MockNextFilter nextFilter = new MockNextFilter("testApp", "UTF-8"); + filter.doFilter(servletRequest, servletResponse, new MockFilterChain(servlet, nextFilter)); + if (null != nextFilter.error) { + throw nextFilter.error; + } + } + + @Test + public void testGetAppNameWithFallback() throws Exception { + when(servletRequest.getHeader(HttpHeaderConsts.APP_FILED)).thenReturn(""); + MockNextFilter nextFilter = new MockNextFilter("unknown", "GBK"); + filter.doFilter(servletRequest, servletResponse, new MockFilterChain(servlet, nextFilter)); + if (null != nextFilter.error) { + throw nextFilter.error; + } + } + + private static class MockNextFilter implements Filter { + + private final String app; + + private final String encoding; + + AssertionError error; + + public MockNextFilter(String app, String encoding) { + this.app = app; + this.encoding = encoding; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + Filter.super.init(filterConfig); + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + try { + RequestContext requestContext = RequestContextHolder.getContext(); + BasicContext basicContext = requestContext.getBasicContext(); + assertEquals("GET /test/path", basicContext.getRequestTarget()); + assertEquals(encoding, basicContext.getEncoding()); + assertEquals("Nacos-Java-Client:v1.4.7", basicContext.getUserAgent()); + assertEquals(app, basicContext.getApp()); + assertEquals("1.1.1.1", basicContext.getAddressContext().getRemoteIp()); + assertEquals("2.2.2.2", basicContext.getAddressContext().getSourceIp()); + assertEquals(3306, basicContext.getAddressContext().getRemotePort()); + assertEquals("localhost", basicContext.getAddressContext().getHost()); + } catch (AssertionFailedError error) { + this.error = error; + } + } + + @Override + public void destroy() { + Filter.super.destroy(); + } + } +} \ No newline at end of file diff --git a/core/src/test/java/com/alibaba/nacos/core/utils/WebUtilsTest.java b/core/src/test/java/com/alibaba/nacos/core/utils/WebUtilsTest.java index 770aba4a92c..9bcedf09df5 100644 --- a/core/src/test/java/com/alibaba/nacos/core/utils/WebUtilsTest.java +++ b/core/src/test/java/com/alibaba/nacos/core/utils/WebUtilsTest.java @@ -18,12 +18,15 @@ import com.alibaba.nacos.common.constant.HttpHeaderConsts; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import org.springframework.mock.web.MockHttpServletRequest; +import javax.servlet.http.HttpServletRequest; import java.nio.charset.StandardCharsets; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.eq; /** * {@link WebUtils} unit tests. @@ -33,6 +36,10 @@ */ class WebUtilsTest { + private static final String X_REAL_IP = "X-Real-IP"; + + private static final String X_FORWARDED_FOR = "X-Forwarded-For"; + @Test void testRequired() { final String key = "key"; @@ -80,4 +87,27 @@ void testGetAcceptEncoding() { servletRequest.addHeader(HttpHeaderConsts.ACCEPT_ENCODING, "gzip, deflate, br"); assertEquals("gzip", WebUtils.getAcceptEncoding(servletRequest)); } + + @Test + void testGetRemoteIp() { + HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + + Mockito.when(request.getRemoteAddr()).thenReturn("127.0.0.1"); + assertEquals("127.0.0.1", WebUtils.getRemoteIp(request)); + + Mockito.when(request.getHeader(eq(X_REAL_IP))).thenReturn("127.0.0.2"); + assertEquals("127.0.0.2", WebUtils.getRemoteIp(request)); + + Mockito.when(request.getHeader(eq(X_FORWARDED_FOR))).thenReturn("127.0.0.3"); + assertEquals("127.0.0.3", WebUtils.getRemoteIp(request)); + + Mockito.when(request.getHeader(eq(X_FORWARDED_FOR))).thenReturn("127.0.0.3, 127.0.0.4"); + assertEquals("127.0.0.3", WebUtils.getRemoteIp(request)); + + Mockito.when(request.getHeader(eq(X_FORWARDED_FOR))).thenReturn(""); + assertEquals("127.0.0.2", WebUtils.getRemoteIp(request)); + + Mockito.when(request.getHeader(eq(X_REAL_IP))).thenReturn(""); + assertEquals("127.0.0.1", WebUtils.getRemoteIp(request)); + } } diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/InstanceController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/InstanceController.java index 1f58e22d6ce..7e4a9e5dc9d 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/InstanceController.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/InstanceController.java @@ -51,6 +51,7 @@ import com.alibaba.nacos.naming.pojo.instance.BeatInfoInstanceBuilder; import com.alibaba.nacos.naming.pojo.instance.HttpRequestInstanceBuilder; import com.alibaba.nacos.naming.pojo.instance.InstanceExtensionHandler; +import com.alibaba.nacos.naming.utils.NamingRequestUtil; import com.alibaba.nacos.naming.web.CanDistro; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; import com.fasterxml.jackson.core.type.TypeReference; @@ -120,7 +121,8 @@ public String register(HttpServletRequest request) throws Exception { .setDefaultInstanceEphemeral(switchDomain.isDefaultInstanceEphemeral()).setRequest(request).build(); getInstanceOperator().registerInstance(namespaceId, serviceName, instance); - NotifyCenter.publishEvent(new RegisterInstanceTraceEvent(System.currentTimeMillis(), "", false, namespaceId, + NotifyCenter.publishEvent(new RegisterInstanceTraceEvent(System.currentTimeMillis(), + NamingRequestUtil.getSourceIpForHttpRequest(request), false, namespaceId, NamingUtils.getGroupName(serviceName), NamingUtils.getServiceName(serviceName), instance.getIp(), instance.getPort())); return "ok"; @@ -145,9 +147,10 @@ public String deregister(HttpServletRequest request) throws Exception { NamingUtils.checkServiceNameFormat(serviceName); getInstanceOperator().removeInstance(namespaceId, serviceName, instance); - NotifyCenter.publishEvent(new DeregisterInstanceTraceEvent(System.currentTimeMillis(), "", false, - DeregisterInstanceReason.REQUEST, namespaceId, NamingUtils.getGroupName(serviceName), - NamingUtils.getServiceName(serviceName), instance.getIp(), instance.getPort())); + NotifyCenter.publishEvent(new DeregisterInstanceTraceEvent(System.currentTimeMillis(), + NamingRequestUtil.getSourceIpForHttpRequest(request), false, DeregisterInstanceReason.REQUEST, + namespaceId, NamingUtils.getGroupName(serviceName), NamingUtils.getServiceName(serviceName), + instance.getIp(), instance.getPort())); return "ok"; } @@ -169,7 +172,8 @@ public String update(HttpServletRequest request) throws Exception { Instance instance = HttpRequestInstanceBuilder.newBuilder() .setDefaultInstanceEphemeral(switchDomain.isDefaultInstanceEphemeral()).setRequest(request).build(); getInstanceOperator().updateInstance(namespaceId, serviceName, instance); - NotifyCenter.publishEvent(new UpdateInstanceTraceEvent(System.currentTimeMillis(), "", namespaceId, + NotifyCenter.publishEvent(new UpdateInstanceTraceEvent(System.currentTimeMillis(), + NamingRequestUtil.getSourceIpForHttpRequest(request), namespaceId, NamingUtils.getGroupName(serviceName), NamingUtils.getServiceName(serviceName), instance.getIp(), instance.getPort(), instance.getMetadata())); return "ok"; @@ -267,7 +271,6 @@ private List parseBatchInstances(String instances) { return Collections.emptyList(); } - /** * Patch instance. * diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/InstanceControllerV2.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/InstanceControllerV2.java index 4a444fde983..a57df409dee 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/InstanceControllerV2.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/InstanceControllerV2.java @@ -56,6 +56,7 @@ import com.alibaba.nacos.naming.pojo.InstanceOperationInfo; import com.alibaba.nacos.naming.pojo.Subscriber; import com.alibaba.nacos.naming.pojo.instance.BeatInfoInstanceBuilder; +import com.alibaba.nacos.naming.utils.NamingRequestUtil; import com.alibaba.nacos.naming.web.CanDistro; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; import com.fasterxml.jackson.core.type.TypeReference; @@ -115,9 +116,9 @@ public Result register(InstanceForm instanceForm) throws NacosException instanceServiceV2.registerInstance(instanceForm.getNamespaceId(), buildCompositeServiceName(instanceForm), instance); NotifyCenter.publishEvent( - new RegisterInstanceTraceEvent(System.currentTimeMillis(), "", false, instanceForm.getNamespaceId(), - instanceForm.getGroupName(), instanceForm.getServiceName(), instance.getIp(), - instance.getPort())); + new RegisterInstanceTraceEvent(System.currentTimeMillis(), NamingRequestUtil.getSourceIp(), false, + instanceForm.getNamespaceId(), instanceForm.getGroupName(), instanceForm.getServiceName(), + instance.getIp(), instance.getPort())); return Result.success("ok"); } @@ -136,9 +137,10 @@ public Result deregister(InstanceForm instanceForm) throws NacosExceptio Instance instance = buildInstance(instanceForm); instanceServiceV2.removeInstance(instanceForm.getNamespaceId(), buildCompositeServiceName(instanceForm), instance); - NotifyCenter.publishEvent(new DeregisterInstanceTraceEvent(System.currentTimeMillis(), "", false, - DeregisterInstanceReason.REQUEST, instanceForm.getNamespaceId(), instanceForm.getGroupName(), - instanceForm.getServiceName(), instance.getIp(), instance.getPort())); + NotifyCenter.publishEvent( + new DeregisterInstanceTraceEvent(System.currentTimeMillis(), NamingRequestUtil.getSourceIp(), false, + DeregisterInstanceReason.REQUEST, instanceForm.getNamespaceId(), instanceForm.getGroupName(), + instanceForm.getServiceName(), instance.getIp(), instance.getPort())); return Result.success("ok"); } @@ -158,9 +160,9 @@ public Result update(InstanceForm instanceForm) throws NacosException { instanceServiceV2.updateInstance(instanceForm.getNamespaceId(), buildCompositeServiceName(instanceForm), instance); NotifyCenter.publishEvent( - new UpdateInstanceTraceEvent(System.currentTimeMillis(), "", instanceForm.getNamespaceId(), - instanceForm.getGroupName(), instanceForm.getServiceName(), instance.getIp(), - instance.getPort(), instance.getMetadata())); + new UpdateInstanceTraceEvent(System.currentTimeMillis(), NamingRequestUtil.getSourceIp(), + instanceForm.getNamespaceId(), instanceForm.getGroupName(), instanceForm.getServiceName(), + instance.getIp(), instance.getPort(), instance.getMetadata())); return Result.success("ok"); } @@ -231,7 +233,6 @@ private List parseBatchInstances(String instances) { return Collections.emptyList(); } - /** * Patch instance. * @@ -463,4 +464,5 @@ private String buildCompositeServiceName(InstanceForm instanceForm) { private String buildCompositeServiceName(InstanceMetadataBatchOperationForm form) { return NamingUtils.getGroupedName(form.getServiceName(), form.getGroupName()); } + } diff --git a/naming/src/main/java/com/alibaba/nacos/naming/core/InstanceOperatorClientImpl.java b/naming/src/main/java/com/alibaba/nacos/naming/core/InstanceOperatorClientImpl.java index 1e632dc2217..fc5d9be4ac2 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/core/InstanceOperatorClientImpl.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/core/InstanceOperatorClientImpl.java @@ -130,8 +130,8 @@ public void updateInstance(String namespaceId, String serviceName, Instance inst throw new NacosApiException(NacosException.INVALID_PARAM, ErrorCode.INSTANCE_ERROR, "service not found, namespace: " + namespaceId + ", service: " + service); } - String metadataId = InstancePublishInfo - .genMetadataId(instance.getIp(), instance.getPort(), instance.getClusterName()); + String metadataId = InstancePublishInfo.genMetadataId(instance.getIp(), instance.getPort(), + instance.getClusterName()); metadataOperateService.updateInstanceMetadata(service, metadataId, buildMetadata(instance)); } @@ -149,8 +149,8 @@ public void patchInstance(String namespaceId, String serviceName, InstancePatchO Service service = getService(namespaceId, serviceName, true); Instance instance = getInstance(namespaceId, serviceName, patchObject.getCluster(), patchObject.getIp(), patchObject.getPort()); - String metadataId = InstancePublishInfo - .genMetadataId(instance.getIp(), instance.getPort(), instance.getClusterName()); + String metadataId = InstancePublishInfo.genMetadataId(instance.getIp(), instance.getPort(), + instance.getClusterName()); Optional instanceMetadata = metadataManager.getInstanceMetadata(service, metadataId); InstanceMetadata newMetadata = instanceMetadata.map(this::cloneMetadata).orElseGet(InstanceMetadata::new); mergeMetadata(newMetadata, patchObject); @@ -189,8 +189,8 @@ public ServiceInfo listInstance(String namespaceId, String serviceName, Subscrib } ServiceInfo serviceInfo = serviceStorage.getData(service); ServiceMetadata serviceMetadata = metadataManager.getServiceMetadata(service).orElse(null); - ServiceInfo result = ServiceUtil - .selectInstancesWithHealthyProtection(serviceInfo, serviceMetadata, cluster, healthOnly, true, subscriber.getIp()); + ServiceInfo result = ServiceUtil.selectInstancesWithHealthyProtection(serviceInfo, serviceMetadata, cluster, + healthOnly, true, subscriber.getIp()); // adapt for v1.x sdk result.setName(NamingUtils.getGroupedName(result.getName(), result.getGroupName())); return result; @@ -329,12 +329,8 @@ private List findBatchUpdateInstance(InstanceOperationInfo instanceOpe private void createIpPortClientIfAbsent(String clientId) { if (!clientManager.contains(clientId)) { - ClientAttributes clientAttributes; - if (ClientAttributesFilter.threadLocalClientAttributes.get() != null) { - clientAttributes = ClientAttributesFilter.threadLocalClientAttributes.get(); - } else { - clientAttributes = new ClientAttributes(); - } + ClientAttributes clientAttributes = ClientAttributesFilter.getCurrentClientAttributes() + .orElse(new ClientAttributes()); clientManager.clientConnected(clientId, clientAttributes); } } diff --git a/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/InstanceRequestHandler.java b/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/InstanceRequestHandler.java index 208e5a3a611..6e00188ef94 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/InstanceRequestHandler.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/InstanceRequestHandler.java @@ -33,6 +33,7 @@ import com.alibaba.nacos.naming.core.v2.pojo.Service; import com.alibaba.nacos.naming.core.v2.service.impl.EphemeralClientOperationServiceImpl; import com.alibaba.nacos.naming.utils.InstanceUtil; +import com.alibaba.nacos.naming.utils.NamingRequestUtil; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; import org.springframework.stereotype.Component; @@ -55,8 +56,8 @@ public InstanceRequestHandler(EphemeralClientOperationServiceImpl clientOperatio @Secured(action = ActionTypes.WRITE) @ExtractorManager.Extractor(rpcExtractor = InstanceRequestParamExtractor.class) public InstanceResponse handle(InstanceRequest request, RequestMeta meta) throws NacosException { - Service service = Service - .newService(request.getNamespace(), request.getGroupName(), request.getServiceName(), true); + Service service = Service.newService(request.getNamespace(), request.getGroupName(), request.getServiceName(), + true); InstanceUtil.setInstanceIdIfEmpty(request.getInstance(), service.getGroupedServiceName()); switch (request.getType()) { case NamingRemoteConstants.REGISTER_INSTANCE: @@ -73,16 +74,17 @@ private InstanceResponse registerInstance(Service service, InstanceRequest reque throws NacosException { clientOperationService.registerInstance(service, request.getInstance(), meta.getConnectionId()); NotifyCenter.publishEvent(new RegisterInstanceTraceEvent(System.currentTimeMillis(), - meta.getClientIp(), true, service.getNamespace(), service.getGroup(), service.getName(), - request.getInstance().getIp(), request.getInstance().getPort())); + NamingRequestUtil.getSourceIpForGrpcRequest(meta), true, service.getNamespace(), service.getGroup(), + service.getName(), request.getInstance().getIp(), request.getInstance().getPort())); return new InstanceResponse(NamingRemoteConstants.REGISTER_INSTANCE); } private InstanceResponse deregisterInstance(Service service, InstanceRequest request, RequestMeta meta) { clientOperationService.deregisterInstance(service, request.getInstance(), meta.getConnectionId()); NotifyCenter.publishEvent(new DeregisterInstanceTraceEvent(System.currentTimeMillis(), - meta.getClientIp(), true, DeregisterInstanceReason.REQUEST, service.getNamespace(), - service.getGroup(), service.getName(), request.getInstance().getIp(), request.getInstance().getPort())); + NamingRequestUtil.getSourceIpForGrpcRequest(meta), true, DeregisterInstanceReason.REQUEST, + service.getNamespace(), service.getGroup(), service.getName(), request.getInstance().getIp(), + request.getInstance().getPort())); return new InstanceResponse(NamingRemoteConstants.DE_REGISTER_INSTANCE); } diff --git a/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/PersistentInstanceRequestHandler.java b/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/PersistentInstanceRequestHandler.java index 3d862219afd..9fac1f5ddcc 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/PersistentInstanceRequestHandler.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/PersistentInstanceRequestHandler.java @@ -35,6 +35,7 @@ import com.alibaba.nacos.naming.core.v2.pojo.Service; import com.alibaba.nacos.naming.core.v2.service.impl.PersistentClientOperationServiceImpl; import com.alibaba.nacos.naming.utils.InstanceUtil; +import com.alibaba.nacos.naming.utils.NamingRequestUtil; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; import org.springframework.stereotype.Component; @@ -45,13 +46,13 @@ */ @Component public class PersistentInstanceRequestHandler extends RequestHandler { - + private final PersistentClientOperationServiceImpl clientOperationService; - + public PersistentInstanceRequestHandler(PersistentClientOperationServiceImpl clientOperationService) { this.clientOperationService = clientOperationService; } - + @Override @TpsControl(pointName = "RemoteNamingInstanceRegisterDeregister", name = "RemoteNamingInstanceRegisterDeregister") @Secured(action = ActionTypes.WRITE) @@ -75,8 +76,9 @@ private InstanceResponse registerInstance(Service service, PersistentInstanceReq Instance instance = request.getInstance(); String clientId = IpPortBasedClient.getClientId(instance.toInetAddr(), false); clientOperationService.registerInstance(service, instance, clientId); - NotifyCenter.publishEvent(new RegisterInstanceTraceEvent(System.currentTimeMillis(), meta.getClientIp(), true, - service.getNamespace(), service.getGroup(), service.getName(), instance.getIp(), instance.getPort())); + NotifyCenter.publishEvent(new RegisterInstanceTraceEvent(System.currentTimeMillis(), + NamingRequestUtil.getSourceIpForGrpcRequest(meta), true, service.getNamespace(), service.getGroup(), + service.getName(), instance.getIp(), instance.getPort())); return new InstanceResponse(NamingRemoteConstants.REGISTER_INSTANCE); } @@ -84,9 +86,9 @@ private InstanceResponse deregisterInstance(Service service, PersistentInstanceR Instance instance = request.getInstance(); String clientId = IpPortBasedClient.getClientId(instance.toInetAddr(), false); clientOperationService.deregisterInstance(service, instance, clientId); - NotifyCenter.publishEvent(new DeregisterInstanceTraceEvent(System.currentTimeMillis(), meta.getClientIp(), true, - DeregisterInstanceReason.REQUEST, service.getNamespace(), service.getGroup(), service.getName(), - instance.getIp(), instance.getPort())); + NotifyCenter.publishEvent(new DeregisterInstanceTraceEvent(System.currentTimeMillis(), + NamingRequestUtil.getSourceIpForGrpcRequest(meta), true, DeregisterInstanceReason.REQUEST, + service.getNamespace(), service.getGroup(), service.getName(), instance.getIp(), instance.getPort())); return new InstanceResponse(NamingRemoteConstants.DE_REGISTER_INSTANCE); } } diff --git a/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/ServiceQueryRequestHandler.java b/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/ServiceQueryRequestHandler.java index aece0393d34..8b1df3c8d80 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/ServiceQueryRequestHandler.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/ServiceQueryRequestHandler.java @@ -30,6 +30,7 @@ import com.alibaba.nacos.naming.core.v2.metadata.NamingMetadataManager; import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata; import com.alibaba.nacos.naming.core.v2.pojo.Service; +import com.alibaba.nacos.naming.utils.NamingRequestUtil; import com.alibaba.nacos.naming.utils.ServiceUtil; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; import org.springframework.stereotype.Component; @@ -65,7 +66,7 @@ public QueryServiceResponse handle(ServiceQueryRequest request, RequestMeta meta ServiceInfo result = serviceStorage.getData(service); ServiceMetadata serviceMetadata = metadataManager.getServiceMetadata(service).orElse(null); result = ServiceUtil.selectInstancesWithHealthyProtection(result, serviceMetadata, cluster, healthyOnly, true, - meta.getClientIp()); + NamingRequestUtil.getSourceIpForGrpcRequest(meta)); return QueryServiceResponse.buildSuccessResponse(result); } } diff --git a/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/SubscribeServiceRequestHandler.java b/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/SubscribeServiceRequestHandler.java index f4822140598..bd90a895c28 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/SubscribeServiceRequestHandler.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/SubscribeServiceRequestHandler.java @@ -27,6 +27,7 @@ import com.alibaba.nacos.common.notify.NotifyCenter; import com.alibaba.nacos.common.trace.event.naming.SubscribeServiceTraceEvent; import com.alibaba.nacos.common.trace.event.naming.UnsubscribeServiceTraceEvent; +import com.alibaba.nacos.core.context.RequestContextHolder; import com.alibaba.nacos.core.control.TpsControl; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.core.paramcheck.impl.SubscribeServiceRequestParamExtractor; @@ -36,6 +37,7 @@ import com.alibaba.nacos.naming.core.v2.pojo.Service; import com.alibaba.nacos.naming.core.v2.service.impl.EphemeralClientOperationServiceImpl; import com.alibaba.nacos.naming.pojo.Subscriber; +import com.alibaba.nacos.naming.utils.NamingRequestUtil; import com.alibaba.nacos.naming.utils.ServiceUtil; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; import org.springframework.stereotype.Component; @@ -70,22 +72,24 @@ public SubscribeServiceResponse handle(SubscribeServiceRequest request, RequestM String namespaceId = request.getNamespace(); String serviceName = request.getServiceName(); String groupName = request.getGroupName(); - String app = request.getHeader("app", "unknown"); + String app = RequestContextHolder.getContext().getBasicContext().getApp(); String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName); Service service = Service.newService(namespaceId, groupName, serviceName, true); Subscriber subscriber = new Subscriber(meta.getClientIp(), meta.getClientVersion(), app, meta.getClientIp(), namespaceId, groupedServiceName, 0, request.getClusters()); ServiceInfo serviceInfo = ServiceUtil.selectInstancesWithHealthyProtection(serviceStorage.getData(service), - metadataManager.getServiceMetadata(service).orElse(null), subscriber.getCluster(), false, - true, subscriber.getIp()); + metadataManager.getServiceMetadata(service).orElse(null), subscriber.getCluster(), false, true, + subscriber.getIp()); if (request.isSubscribe()) { clientOperationService.subscribeService(service, subscriber, meta.getConnectionId()); NotifyCenter.publishEvent(new SubscribeServiceTraceEvent(System.currentTimeMillis(), - meta.getClientIp(), service.getNamespace(), service.getGroup(), service.getName())); + NamingRequestUtil.getSourceIpForGrpcRequest(meta), service.getNamespace(), service.getGroup(), + service.getName())); } else { clientOperationService.unsubscribeService(service, subscriber, meta.getConnectionId()); NotifyCenter.publishEvent(new UnsubscribeServiceTraceEvent(System.currentTimeMillis(), - meta.getClientIp(), service.getNamespace(), service.getGroup(), service.getName())); + NamingRequestUtil.getSourceIpForGrpcRequest(meta), service.getNamespace(), service.getGroup(), + service.getName())); } return new SubscribeServiceResponse(ResponseCode.SUCCESS.getCode(), "success", serviceInfo); } diff --git a/naming/src/main/java/com/alibaba/nacos/naming/utils/NamingRequestUtil.java b/naming/src/main/java/com/alibaba/nacos/naming/utils/NamingRequestUtil.java new file mode 100644 index 00000000000..feed51b4e3b --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/utils/NamingRequestUtil.java @@ -0,0 +1,77 @@ +/* + * Copyright 1999-2023 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.utils; + +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.context.RequestContextHolder; +import com.alibaba.nacos.core.context.addition.AddressContext; +import com.alibaba.nacos.core.utils.WebUtils; + +import javax.servlet.http.HttpServletRequest; + +/** + * Naming request util. + * + * @author xiweng.yy + */ +public class NamingRequestUtil { + + /** + * Get source ip from request context. + * + * @return source ip, null if not found + */ + public static String getSourceIp() { + AddressContext addressContext = RequestContextHolder.getContext().getBasicContext().getAddressContext(); + String sourceIp = addressContext.getSourceIp(); + if (StringUtils.isBlank(sourceIp)) { + sourceIp = addressContext.getRemoteIp(); + } + return sourceIp; + } + + /** + * Get source ip from request context first, if it can't found, get from http request. + * + * @param httpServletRequest http request + * @return source ip, null if not found + */ + public static String getSourceIpForHttpRequest(HttpServletRequest httpServletRequest) { + String sourceIp = getSourceIp(); + // If can't get from request context, get from http request. + if (StringUtils.isBlank(sourceIp)) { + sourceIp = WebUtils.getRemoteIp(httpServletRequest); + } + return sourceIp; + } + + /** + * Get source ip from request context first, if it can't found, get from http request. + * + * @param meta grpc request meta + * @return source ip, null if not found + */ + public static String getSourceIpForGrpcRequest(RequestMeta meta) { + String sourceIp = getSourceIp(); + // If can't get from request context, get from grpc request meta. + if (StringUtils.isBlank(sourceIp)) { + sourceIp = meta.getClientIp(); + } + return sourceIp; + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/web/ClientAttributesFilter.java b/naming/src/main/java/com/alibaba/nacos/naming/web/ClientAttributesFilter.java index 8a3638bc23a..99a91f33bd3 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/web/ClientAttributesFilter.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/web/ClientAttributesFilter.java @@ -20,6 +20,7 @@ import com.alibaba.nacos.common.utils.HttpMethod; import com.alibaba.nacos.common.utils.InternetAddressUtil; import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.context.RequestContextHolder; import com.alibaba.nacos.core.utils.WebUtils; import com.alibaba.nacos.naming.core.v2.client.ClientAttributes; import com.alibaba.nacos.naming.core.v2.client.impl.IpPortBasedClient; @@ -35,6 +36,7 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; +import java.util.Optional; /** *

@@ -56,7 +58,14 @@ public class ClientAttributesFilter implements Filter { @Autowired private ClientManager clientManager; - public static ThreadLocal threadLocalClientAttributes = new ThreadLocal<>(); + public static Optional getCurrentClientAttributes() { + Object clientAttributes = RequestContextHolder.getContext() + .getExtensionContext(ClientAttributes.class.getSimpleName()); + if (clientAttributes instanceof ClientAttributes) { + return Optional.of((ClientAttributes) clientAttributes); + } + return Optional.empty(); + } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) @@ -65,38 +74,32 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo String uri = request.getRequestURI(); String method = request.getMethod(); try { - try { - if (isRegisterInstanceUri(uri, method)) { - //register - ClientAttributes requestClientAttributes = getClientAttributes(request); - threadLocalClientAttributes.set(requestClientAttributes); - } else if (isBeatUri(uri, method)) { - //beat - String ip = WebUtils.optional(request, IP, StringUtils.EMPTY); - int port = Integer.parseInt(WebUtils.optional(request, PORT, ZERO)); - String clientId = IpPortBasedClient - .getClientId(ip + InternetAddressUtil.IP_PORT_SPLITER + port, true); - IpPortBasedClient client = (IpPortBasedClient) clientManager.getClient(clientId); - if (client != null) { - ClientAttributes requestClientAttributes = getClientAttributes(request); - //update clientAttributes,when client version attributes is null,then update. - if (canUpdateClientAttributes(client, requestClientAttributes)) { - client.setAttributes(requestClientAttributes); - } + if (isRegisterInstanceUri(uri, method)) { + //register + ClientAttributes attributes = getClientAttributes(); + RequestContextHolder.getContext() + .addExtensionContext(ClientAttributes.class.getSimpleName(), attributes); + } else if (isBeatUri(uri, method)) { + //beat + String ip = WebUtils.optional(request, IP, StringUtils.EMPTY); + int port = Integer.parseInt(WebUtils.optional(request, PORT, ZERO)); + String clientId = IpPortBasedClient.getClientId(ip + InternetAddressUtil.IP_PORT_SPLITER + port, true); + IpPortBasedClient client = (IpPortBasedClient) clientManager.getClient(clientId); + if (client != null) { + ClientAttributes requestClientAttributes = getClientAttributes(); + //update clientAttributes,when client version attributes is null,then update. + if (canUpdateClientAttributes(client, requestClientAttributes)) { + client.setAttributes(requestClientAttributes); } } - } catch (Exception e) { - Loggers.SRV_LOG.error("handler client attributes error", e); - } - try { - filterChain.doFilter(request, servletResponse); - } catch (ServletException e) { - throw new RuntimeException(e); - } - } finally { - if (threadLocalClientAttributes.get() != null) { - threadLocalClientAttributes.remove(); } + } catch (Exception e) { + Loggers.SRV_LOG.error("handler client attributes error", e); + } + try { + filterChain.doFilter(request, servletResponse); + } catch (ServletException e) { + throw new RuntimeException(e); } } @@ -104,19 +107,18 @@ private boolean isBeatUri(String uri, String httpMethod) { return ((UtilsAndCommons.NACOS_SERVER_CONTEXT + UtilsAndCommons.NACOS_NAMING_CONTEXT + UtilsAndCommons.NACOS_NAMING_INSTANCE_CONTEXT + BEAT_URI).equals(uri) || ( UtilsAndCommons.NACOS_SERVER_CONTEXT + UtilsAndCommons.DEFAULT_NACOS_NAMING_CONTEXT_V2 - + UtilsAndCommons.NACOS_NAMING_INSTANCE_CONTEXT + BEAT_URI).equals(uri)) && HttpMethod.PUT - .equals(httpMethod); + + UtilsAndCommons.NACOS_NAMING_INSTANCE_CONTEXT + BEAT_URI).equals(uri)) + && HttpMethod.PUT.equals(httpMethod); } private boolean isRegisterInstanceUri(String uri, String httpMethod) { return ((UtilsAndCommons.NACOS_SERVER_CONTEXT + UtilsAndCommons.NACOS_NAMING_CONTEXT + UtilsAndCommons.NACOS_NAMING_INSTANCE_CONTEXT).equals(uri) || (UtilsAndCommons.NACOS_SERVER_CONTEXT - + UtilsAndCommons.DEFAULT_NACOS_NAMING_CONTEXT_V2 + UtilsAndCommons.NACOS_NAMING_INSTANCE_CONTEXT) - .equals(uri)) && HttpMethod.POST.equals(httpMethod); + + UtilsAndCommons.DEFAULT_NACOS_NAMING_CONTEXT_V2 + + UtilsAndCommons.NACOS_NAMING_INSTANCE_CONTEXT).equals(uri)) && HttpMethod.POST.equals(httpMethod); } - private static boolean canUpdateClientAttributes(IpPortBasedClient client, - ClientAttributes requestClientAttributes) { + private boolean canUpdateClientAttributes(IpPortBasedClient client, ClientAttributes requestClientAttributes) { if (requestClientAttributes.getClientAttribute(HttpHeaderConsts.CLIENT_VERSION_HEADER) == null) { return false; } @@ -127,10 +129,10 @@ private static boolean canUpdateClientAttributes(IpPortBasedClient client, return true; } - public static ClientAttributes getClientAttributes(HttpServletRequest request) { - String version = request.getHeader(HttpHeaderConsts.CLIENT_VERSION_HEADER); - String app = request.getHeader(HttpHeaderConsts.APP_FILED); - String clientIp = request.getRemoteAddr(); + private ClientAttributes getClientAttributes() { + String version = RequestContextHolder.getContext().getBasicContext().getUserAgent(); + String app = RequestContextHolder.getContext().getBasicContext().getApp(); + String clientIp = RequestContextHolder.getContext().getBasicContext().getAddressContext().getSourceIp(); ClientAttributes clientAttributes = new ClientAttributes(); if (version != null) { clientAttributes.addClientAttribute(HttpHeaderConsts.CLIENT_VERSION_HEADER, version); diff --git a/naming/src/test/java/com/alibaba/nacos/naming/remote/rpc/handler/SubscribeServiceRequestHandlerTest.java b/naming/src/test/java/com/alibaba/nacos/naming/remote/rpc/handler/SubscribeServiceRequestHandlerTest.java index 665589ef49e..673792f30dd 100644 --- a/naming/src/test/java/com/alibaba/nacos/naming/remote/rpc/handler/SubscribeServiceRequestHandlerTest.java +++ b/naming/src/test/java/com/alibaba/nacos/naming/remote/rpc/handler/SubscribeServiceRequestHandlerTest.java @@ -23,12 +23,14 @@ import com.alibaba.nacos.api.naming.remote.request.SubscribeServiceRequest; import com.alibaba.nacos.api.naming.remote.response.SubscribeServiceResponse; import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.core.context.RequestContextHolder; import com.alibaba.nacos.naming.core.v2.index.ServiceStorage; import com.alibaba.nacos.naming.core.v2.metadata.NamingMetadataManager; import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata; import com.alibaba.nacos.naming.core.v2.service.impl.EphemeralClientOperationServiceImpl; import com.alibaba.nacos.naming.selector.SelectorManager; import com.alibaba.nacos.sys.utils.ApplicationUtils; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -78,6 +80,11 @@ void setUp() { Mockito.when(applicationContext.getBean(SelectorManager.class)).thenReturn(selectorManager); } + @AfterEach + void tearDown() { + RequestContextHolder.removeContext(); + } + @Test void testHandle() throws NacosException { Instance instance = new Instance(); diff --git a/naming/src/test/java/com/alibaba/nacos/naming/utils/NamingRequestUtilTest.java b/naming/src/test/java/com/alibaba/nacos/naming/utils/NamingRequestUtilTest.java new file mode 100644 index 00000000000..cf1e6e8581c --- /dev/null +++ b/naming/src/test/java/com/alibaba/nacos/naming/utils/NamingRequestUtilTest.java @@ -0,0 +1,82 @@ +/* + * Copyright 1999-2023 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.utils; + +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.core.context.RequestContextHolder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.servlet.http.HttpServletRequest; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class NamingRequestUtilTest { + + @Mock + HttpServletRequest request; + + @Mock + RequestMeta meta; + + @BeforeEach + void setUp() { + RequestContextHolder.getContext().getBasicContext().getAddressContext().setRemoteIp("1.1.1.1"); + RequestContextHolder.getContext().getBasicContext().getAddressContext().setSourceIp("2.2.2.2"); + } + + @AfterEach + void tearDown() { + RequestContextHolder.removeContext(); + } + + @Test + void testGetSourceIp() { + assertEquals("2.2.2.2", NamingRequestUtil.getSourceIp()); + RequestContextHolder.getContext().getBasicContext().getAddressContext().setSourceIp(null); + assertEquals("1.1.1.1", NamingRequestUtil.getSourceIp()); + RequestContextHolder.getContext().getBasicContext().getAddressContext().setRemoteIp(null); + assertNull(NamingRequestUtil.getSourceIp()); + } + + @Test + void getSourceIpForHttpRequest() { + when(request.getRemoteAddr()).thenReturn("3.3.3.3"); + assertEquals("2.2.2.2", NamingRequestUtil.getSourceIpForHttpRequest(request)); + RequestContextHolder.getContext().getBasicContext().getAddressContext().setSourceIp(null); + assertEquals("1.1.1.1", NamingRequestUtil.getSourceIpForHttpRequest(request)); + RequestContextHolder.getContext().getBasicContext().getAddressContext().setRemoteIp(null); + assertEquals("3.3.3.3", NamingRequestUtil.getSourceIpForHttpRequest(request)); + } + + @Test + void getSourceIpForGrpcRequest() { + when(meta.getClientIp()).thenReturn("3.3.3.3"); + assertEquals("2.2.2.2", NamingRequestUtil.getSourceIpForGrpcRequest(meta)); + RequestContextHolder.getContext().getBasicContext().getAddressContext().setSourceIp(null); + assertEquals("1.1.1.1", NamingRequestUtil.getSourceIpForGrpcRequest(meta)); + RequestContextHolder.getContext().getBasicContext().getAddressContext().setRemoteIp(null); + assertEquals("3.3.3.3", NamingRequestUtil.getSourceIpForGrpcRequest(meta)); + } +} \ No newline at end of file diff --git a/naming/src/test/java/com/alibaba/nacos/naming/web/ClientAttributesFilterTest.java b/naming/src/test/java/com/alibaba/nacos/naming/web/ClientAttributesFilterTest.java new file mode 100644 index 00000000000..d8d92b8a95f --- /dev/null +++ b/naming/src/test/java/com/alibaba/nacos/naming/web/ClientAttributesFilterTest.java @@ -0,0 +1,121 @@ +/* + * Copyright 1999-2023 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.web; + +import com.alibaba.nacos.common.constant.HttpHeaderConsts; +import com.alibaba.nacos.core.context.RequestContextHolder; +import com.alibaba.nacos.naming.core.v2.client.ClientAttributes; +import com.alibaba.nacos.naming.core.v2.client.impl.IpPortBasedClient; +import com.alibaba.nacos.naming.core.v2.client.manager.ClientManager; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockFilterChain; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ClientAttributesFilterTest { + + @Mock + ClientManager clientManager; + + @Mock + IpPortBasedClient client; + + @Mock + HttpServletRequest request; + + @Mock + HttpServletResponse response; + + @Mock + Servlet servlet; + + @InjectMocks + ClientAttributesFilter filter; + + @BeforeEach + void setUp() { + RequestContextHolder.getContext().getBasicContext().setUserAgent("Nacos-Java-Client:v2.4.0"); + RequestContextHolder.getContext().getBasicContext().setApp("testApp"); + RequestContextHolder.getContext().getBasicContext().getAddressContext().setRemoteIp("1.1.1.1"); + RequestContextHolder.getContext().getBasicContext().getAddressContext().setSourceIp("2.2.2.2"); + } + + @AfterEach + void tearDown() { + RequestContextHolder.removeContext(); + } + + @Test + void testDoFilterForRegisterUri() throws IOException { + when(request.getRequestURI()).thenReturn( + UtilsAndCommons.NACOS_SERVER_CONTEXT + UtilsAndCommons.NACOS_NAMING_CONTEXT + + UtilsAndCommons.NACOS_NAMING_INSTANCE_CONTEXT); + when(request.getMethod()).thenReturn("POST"); + filter.doFilter(request, response, new MockFilterChain(servlet, new MockRegisterFilter())); + } + + @Test + void testDoFilterForBeatUri() throws IOException { + when(request.getParameter("ip")).thenReturn("127.0.0.1"); + when(request.getParameter("port")).thenReturn("8848"); + when(request.getParameter("encoding")).thenReturn("utf-8"); + when(clientManager.getClient("127.0.0.1:8848#true")).thenReturn(client); + when(request.getRequestURI()).thenReturn( + UtilsAndCommons.NACOS_SERVER_CONTEXT + UtilsAndCommons.NACOS_NAMING_CONTEXT + + UtilsAndCommons.NACOS_NAMING_INSTANCE_CONTEXT + "/beat"); + when(request.getMethod()).thenReturn("PUT"); + filter.doFilter(request, response, new MockFilterChain()); + verify(client).setAttributes(any(ClientAttributes.class)); + } + + private static class MockRegisterFilter implements Filter { + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + Optional clientAttributes = ClientAttributesFilter.getCurrentClientAttributes(); + assertTrue(clientAttributes.isPresent()); + assertEquals("Nacos-Java-Client:v2.4.0", + clientAttributes.get().getClientAttribute(HttpHeaderConsts.CLIENT_VERSION_HEADER)); + assertEquals("testApp", clientAttributes.get().getClientAttribute(HttpHeaderConsts.APP_FILED)); + assertEquals("2.2.2.2", clientAttributes.get().getClientAttribute(HttpHeaderConsts.CLIENT_IP)); + } + } +} \ No newline at end of file diff --git a/style/NacosCheckStyle_9.xml b/style/NacosCheckStyle_9.xml new file mode 100644 index 00000000000..6c46c983eb7 --- /dev/null +++ b/style/NacosCheckStyle_9.xml @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/style/codeStyle.md b/style/codeStyle.md index 9cf424ff86e..5ecbb8fce02 100644 --- a/style/codeStyle.md +++ b/style/codeStyle.md @@ -53,6 +53,8 @@ Volunteer wanted. 3. Import `style/NacosCheckStyle.xml` to checkstyle plugin. 4. Scan and check your modified code by plugin. +> If you install the latest version of CheckStyle plugin, it may not support the previous version of CheckStyle(9.0), you can modify the `style/NacosCheckStyle.xml` file to `style/NacosCheckStyle_9.xml` instead. + [checkstyle插件idea安装](https://plugins.jetbrains.com/plugin/1065-checkstyle-idea) 1. `Preferences/Settings --> Other Settings --> Checkstyle` 或者 `Preferences/Settings --> Tools --> Checkstyle` @@ -60,6 +62,8 @@ Volunteer wanted. 3. 导入源代码下`style/NacosCheckStyle.xml`文件到checkstyle插件。 4. 用checkstyle插件扫描你修改的代码。 +> 如果安装的CheckStyle的插件较新,已不支持9.0之前的Checkstyle版本,将上述第3步的`style/NacosCheckStyle.xml`文件修改为`style/NacosCheckStyle_9.xml`即可。 + ### eclipse IDE #### p3c