请求过程
用户在本地通过浏览器页面发起请求,请求经过nginx代理,转发到SpringCloud框架的微服务A模块,A服务模块通过Feign调用服务提供者B模块
实现功能
在B模块中想要获取到用户主机的真实ip地址,以及浏览器页面上的请求路径
遇到问题
1、由于经过nginx代理,直接通过request.getRemoteAddr();获取到的是nginx服务器的地址,而不是客户端的真实ip地址
2、由于经过了feign请求,直接通过request.getRequestURL();获取到的是请求B模块的url路径,而不是客户端请求的路径。
解决方法
通过修改nginx的配置文件、将客户端的真实ip地址,客户端的真实请求路径放入请求的header中,在B模块获取各种参数时,通过获取request的请求头进而获取。
1、修改nginx配置文件,并刷新配置
在匹配的location {}模块中,添加如下参数
proxy_set_header Host $host;
proxy_set_header X-real-ip $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header uri $uri;
proxy_set_header client_origin $http_origin;
![](https://i-blog.csdnimg.cn/blog_migrate/9f3ae309395896f7c4aefd80bfd79aaa.png)
2、在项目中进行接收
String host = "127.0.0.1:8888";
String ip = "127.0.0.1";
String uri = "127.0.0.1:8888/login";
HttpServletRequest request = null;
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
if (requestAttributes != null) {
request = ((ServletRequestAttributes) requestAttributes).getRequest();
if (request!=null){
ip = HttpClientUtil.getInstance().getClientIP(request);
if (request.getLocalAddr()!=null&&!"".equals(request.getLocalAddr())){
host = request.getLocalAddr() + ":" + request.getLocalPort();
}
String origin = request.getHeader("client_origin");
String client_uri = request.getHeader("uri");
if (origin!=null&&!"".equals(origin)&&client_uri!=null&&!"".equals(client_uri)){
uri = origin + client_uri;
}
}
}
requestMap.put("host",host);
requestMap.put("client_ip",ip);
requestMap.put("uri",uri);
getClientIP是自己写的一个帮助方法,如下
/**
* 获取真实的客户端ip
* @param request
* @return
*/
public String getClientIP(HttpServletRequest request){
String ip = request.getHeader("X-Forwarded-For");
if (!StringUtils.isEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
//多次反向代理后会有多个ip值,第一个ip才是真实ip
int index = ip.indexOf(",");
if (index != -1) {
return ip.substring(0, index);
} else {
return ip;
}
}
ip = request.getHeader("X-real-ip");
if (!StringUtils.isEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
return ip;
}
return request.getRemoteAddr();
}
一般来说,经过上面的设置之后就可以取到nginx中设置的header头部参数了,但是我取到的值竟然为null
后来经过查证才明白,在通过feign进行服务的远程调用时,会把header给丢失了,纳尼?为什么?这不是难为我这个18岁的老同志吗。我劝feign这个年轻人要耗子尾汁,但是没有办法,bug还是要修复的。
解决办法
在微服务A模块,及即eign远程调用的消费者端,添加一个拦截器,对A服务接收到的request请求进行拦截,将其加入到RequestTemplate中去
拦截器
package com.github.wxiaoqi.security.auth.configuration;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
@Slf4j
@Configuration
public class FeginHeaderInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String values = request.getHeader(name);
template.header(name, values);
}
}
Enumeration<String> bodyNames = request.getParameterNames();
StringBuffer body =new StringBuffer();
if (bodyNames != null) {
while (bodyNames.hasMoreElements()) {
String name = bodyNames.nextElement();
String values = request.getParameter(name);
body.append(name).append("=").append(values).append("&");
}
}
if(body.length()!=0) {
body.deleteCharAt(body.length()-1);
template.body(body.toString());
}
}
}
配置拦截器
在A模块,即feign消费者的远程接口调用方法中,设置此拦截器
@FeignClient(value = "ace-admin",configuration = FeginHeaderInterceptor.class)
public interface IUserService {
@RequestMapping(value = "/api/user/validate", method = RequestMethod.POST)
public UserInfo validate(@RequestBody JwtAuthenticationRequest authenticationRequest);
}
以上的配置完成后,就可以实现在feign的微服务提供者端,获取真实客户端的各种参数了。我看有的人说还需要 指定Feign的隔离策略为:SEMAPHORE,因为ThreadLocal什么的,但是我没有设置就成功了,所以大家可以自己尝试下
其实无论是通过nginx代理获取到真实ip地址,还是feign远程调用导出的header丢失问题,大家都有分享自己的处理方法。但是像我这种倒霉的,既遇到nginx代理导致真实ip被覆盖,又因为feign导致header丢失的情况,没有太多人出现,所以就总结一下吧!
不过还是有很多的疑惑,比如:nginx里有哪些$开头的参数,feign为什么会把header丢失了,如果大家知晓的话,还请赐教。