001package top.cenze.utils.http.request;
002
003import cn.hutool.core.collection.CollectionUtil;
004import cn.hutool.core.convert.Convert;
005import cn.hutool.core.util.ObjectUtil;
006import cn.hutool.core.util.StrUtil;
007import com.alibaba.fastjson.JSON;
008import com.alibaba.fastjson.JSONObject;
009import io.netty.buffer.ByteBufAllocator;
010import lombok.extern.slf4j.Slf4j;
011import org.springframework.cloud.gateway.filter.GatewayFilterChain;
012import org.springframework.core.io.buffer.DataBuffer;
013import org.springframework.core.io.buffer.DataBufferUtils;
014import org.springframework.core.io.buffer.NettyDataBufferFactory;
015import org.springframework.http.HttpHeaders;
016import org.springframework.http.HttpMethod;
017import org.springframework.http.MediaType;
018import org.springframework.http.server.reactive.ServerHttpRequest;
019import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
020import org.springframework.web.server.ServerWebExchange;
021import org.springframework.web.util.UriComponentsBuilder;
022import reactor.core.publisher.Flux;
023import reactor.core.publisher.Mono;
024import top.cenze.utils.aspect.AspectUtil;
025
026import java.net.InetAddress;
027import java.net.URI;
028import java.net.UnknownHostException;
029import java.nio.CharBuffer;
030import java.nio.charset.StandardCharsets;
031import java.util.HashMap;
032import java.util.Map;
033import java.util.concurrent.atomic.AtomicReference;
034
035@Slf4j
036public class GatewayUtil {
037    // 多次反向代理后会有多个ip值 的分割符
038    private final static String IP_UTILS_FLAG = ",";
039    // 未知IP
040    private final static String UNKNOWN = "unknown";
041    // 本地 IP
042    private final static String LOCALHOST_IP = "0:0:0:0:0:0:0:1";
043    private final static String LOCALHOST_IP1 = "127.0.0.1";
044
045    /**
046     * 获取TOKEN
047     * @param exchange
048     * @return
049     */
050    public static String getToken(ServerWebExchange exchange) {
051        // 获取请求
052        ServerHttpRequest request = exchange.getRequest();
053
054        String token = request.getHeaders().getFirst("TOKEN");
055        if (StrUtil.isEmpty(token)) {
056            token = getParameter(exchange, "TOKEN");
057        }
058
059        return token;
060    }
061
062    /**
063     * 获取请求参数
064     * @param exchange
065     * @param name
066     * @return
067     */
068    public static String getParameter(ServerWebExchange exchange, String name) {
069        // 获取请求
070        ServerHttpRequest request = exchange.getRequest();
071
072        // 获取方法
073        HttpMethod method = request.getMethod();
074
075        // POST或PUT方法
076        if (HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method)) {
077            String body = resolveBodyFromRequest(request);
078            JSONObject json = null;
079            if (StrUtil.isNotEmpty(body)) {
080                json = JSON.parseObject(body);
081            } else {
082                json = new JSONObject();
083            }
084            log.info("getParameter json: {}", json.toJSONString());
085
086            if (ObjectUtil.isNotNull(json) && ObjectUtil.isNotNull(json.get(name))) {
087                return Convert.toStr(json.get(name));
088            }
089        }
090        // GET方法
091        else if (HttpMethod.GET.equals(method)) {
092            return request.getQueryParams().getFirst(name);
093        }
094
095        return null;
096    }
097
098    /**
099     * 设置请求参数
100     * 调用此方法后,需要在调用rewriteRequestParams重写request
101     * @param exchange
102     * @param chain
103     * @param name
104     * @param value
105     * @return
106     */
107    public static String setParameters(ServerWebExchange exchange, GatewayFilterChain chain, String name, Object value) {
108        Map<String, Object> mapParams = new HashMap<>();
109        mapParams.put(name, value);
110
111        return setParameters(exchange, chain, mapParams);
112    }
113
114    /**
115     * 设置请求参数集合
116     * 调用此方法后,需要在调用rewriteRequestParams重写request
117     * @param exchange
118     * @param chain
119     * @param mapParams
120     * @return
121     */
122    public static String setParameters(ServerWebExchange exchange, GatewayFilterChain chain, Map<String, Object> mapParams) {
123        // 获取请求
124        ServerHttpRequest request = exchange.getRequest();
125        // 获取方法
126        HttpMethod method = request.getMethod();
127        // 参数文本类型
128        String contentType = request.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
129
130        // POST、JSON参数、或表单格式(如上传文件等)
131        if (HttpMethod.POST.equals(method) &&
132                (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equalsIgnoreCase(contentType)
133                        || MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(contentType))) {
134            // 获取请求体字符串
135            String strBody = resolveBodyFromRequest(request);
136            if (StrUtil.isEmpty(strBody)) {
137                return null;
138            }
139
140            // application/x-www-form-urlencoded
141            // 其他上传文件之类的,不做参数处理,因为文件流添加参数,文件原格式就会出问题了
142            if (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equalsIgnoreCase(contentType)) {
143                // 普通键值对,增加参数
144                if (CollectionUtil.isNotEmpty(mapParams)) {
145                    StringBuilder sb = new StringBuilder();
146                    for (Map.Entry<String, Object> entry : mapParams.entrySet()) {
147                        sb.append("&").append(entry.getKey()).append("=").append(entry.getValue());
148                    }
149
150                    return strBody + sb.toString();
151                }
152            }
153            // application/json
154            else if (MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(contentType)) {
155                JSONObject json = null;
156                if (StrUtil.isNotEmpty(strBody)) {
157                    json = JSON.parseObject(strBody);
158                } else {
159                    json = new JSONObject();
160                }
161                log.info("getParameter json: {}", json.toJSONString());
162
163                // 添加/修改参数
164                if (CollectionUtil.isNotEmpty(mapParams)) {
165                    for (Map.Entry<String, Object> entry : mapParams.entrySet()) {
166                        json.put(entry.getKey(), entry.getValue());
167                    }
168
169                    return json.toJSONString();
170                }
171            }
172        }
173        // GET方法
174        else if (HttpMethod.GET.equals(method)) {
175            // 获取原参数
176            URI uri = request.getURI();
177            StringBuilder query = new StringBuilder();
178            String originalQuery = uri.getRawQuery();
179            if (org.springframework.util.StringUtils.hasText(originalQuery)) {
180                query.append(originalQuery);
181                if (originalQuery.charAt(originalQuery.length() - 1) != '&') {
182                    query.append('&');
183                }
184            }
185            // 添加查询参数
186            if (CollectionUtil.isNotEmpty(mapParams)) {
187                StringBuilder sb = new StringBuilder();
188                for (Map.Entry<String, Object> entry : mapParams.entrySet()) {
189                    query.append("&").append(entry.getKey()).append("=").append(entry.getValue());
190                }
191
192                return query.toString();
193            }
194        }
195
196        return null;
197    }
198
199    /**
200     * 重写请求参数
201     * @param exchange
202     * @param chain
203     * @param strBody
204     * @return
205     */
206    public static Mono<Void> rewriteRequestParams(ServerWebExchange exchange, GatewayFilterChain chain, String strBody) {
207        if (StrUtil.isEmpty(strBody)) {
208            return chain.filter(exchange);
209        }
210
211        // 获取请求
212        ServerHttpRequest request = exchange.getRequest();
213        // 获取方法
214        HttpMethod method = request.getMethod();
215        // 参数文本类型
216        String contentType = request.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
217        // 获取原uri
218        URI uri = request.getURI();
219
220        // POST、JSON参数、或表单格式(如上传文件等)
221        if (HttpMethod.POST.equals(method) &&
222                (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equalsIgnoreCase(contentType)
223                        || MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(contentType))) {
224            //下面的将请求体再次封装写回到request里,传到下一级,否则,由于请求体已被消费,后续的服务将取不到值
225            URI newUri = UriComponentsBuilder.fromUri(uri).build(true).toUri();
226            request = exchange.getRequest().mutate().uri(newUri).build();
227            DataBuffer dataBuffer = stringToDataBuffer(strBody);
228            Flux<DataBuffer> bodyFlux = Flux.just(dataBuffer);
229
230            // 定义新的消息头
231            HttpHeaders headers = new HttpHeaders();
232            headers.putAll(exchange.getRequest().getHeaders());
233
234            // 由于修改了传递参数,需要重新设置CONTENT_LENGTH,长度是字节长度,不是字符串长度
235            int length = strBody.getBytes().length;
236            headers.remove(HttpHeaders.CONTENT_LENGTH);
237            headers.setContentLength(length);
238
239            // 设置CONTENT_TYPE
240            if (StrUtil.isNotEmpty(contentType)) {
241                headers.set(HttpHeaders.CONTENT_TYPE, contentType);
242            }
243
244            // 由于post的body只能订阅一次,由于上面代码中已经订阅过一次body。所以要再次封装请求到request才行,不然会报错请求已经订阅过
245            request = new ServerHttpRequestDecorator(request) {
246                @Override
247                public HttpHeaders getHeaders() {
248                    long contentLength = headers.getContentLength();
249                    HttpHeaders httpHeaders = new HttpHeaders();
250                    httpHeaders.putAll(super.getHeaders());
251                    if (contentLength > 0) {
252                        httpHeaders.setContentLength(contentLength);
253                    } else {
254                        // TODO: this causes a 'HTTP/1.1 411 Length Required' on httpbin.org
255                        httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
256                    }
257                    return httpHeaders;
258                }
259
260                @Override
261                public Flux<DataBuffer> getBody() {
262                    return bodyFlux;
263                }
264            };
265
266            //封装request,传给下一级
267            request.mutate().header(HttpHeaders.CONTENT_LENGTH, Integer.toString(strBody.length()));
268            return chain.filter(exchange.mutate().request(request).build());
269        }
270        // GET方法
271        else if (HttpMethod.GET.equals(method)) {
272            // 替换查询参数
273            URI newUri = UriComponentsBuilder.fromUri(uri)
274                    .replaceQuery(strBody)
275                    .build(true)
276                    .toUri();
277
278            request = exchange.getRequest().mutate().uri(newUri).build();
279            return chain.filter(exchange.mutate().request(request).build());
280        }
281
282        return chain.filter(exchange);
283    }
284
285    /**
286     * 从Flux<DataBuffer>中获取字符串的方法
287     * @return 请求体
288     */
289    private static String resolveBodyFromRequest(ServerHttpRequest request) {
290        //获取请求体
291        Flux<DataBuffer> body = request.getBody();
292
293        AtomicReference<String> bodyRef = new AtomicReference<>();
294        body.subscribe(buffer -> {
295            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
296            DataBufferUtils.release(buffer);
297            bodyRef.set(charBuffer.toString());
298        });
299
300        //获取request body
301        return bodyRef.get();
302    }
303
304    /**
305     * 字符串转DataBuffer
306     * @param value
307     * @return
308     */
309    private static DataBuffer stringToDataBuffer(String value) {
310        byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
311        NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
312        DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
313        buffer.write(bytes);
314        return buffer;
315    }
316
317    /**
318     * 获取访问者真实ip
319     * @param exchange
320     * @return
321     */
322    public static String getRemoteIP(ServerWebExchange exchange) {
323        // 获取请求
324        ServerHttpRequest request = exchange.getRequest();
325
326        return getRemoteIP(request);
327    }
328
329    /**
330     * 获取访问者真实ip
331     * @param request
332     * @return
333     */
334    public static String getRemoteIP(ServerHttpRequest request) {
335        String ip = null;
336        try {
337            // 以下两个获取在k8s中,真实的客户端IP,放到了x-Original-Forwarded-For。而WAF的回源地址放到了 x-Forwarded-For了。
338            ip = request.getHeaders().getFirst("X-Original-Forwarded-For");
339            if (StrUtil.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
340                ip = request.getHeaders().getFirst("X-Forwarded-For");
341            }
342            // 获取nginx等代理的ip
343            if (StrUtil.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
344                ip = request.getHeaders().getFirst("x-forwarded-for");
345            }
346            if (StrUtil.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
347                ip = request.getHeaders().getFirst("Proxy-Client-IP");
348            }
349            if (StrUtil.isEmpty(ip) || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
350                ip = request.getHeaders().getFirst("WL-Proxy-Client-IP");
351            }
352            if (StrUtil.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
353                ip = request.getHeaders().getFirst("HTTP_CLIENT_IP");
354            }
355            if (StrUtil.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
356                ip = request.getHeaders().getFirst("HTTP_X_FORWARDED_FOR");
357            }
358            if (StrUtil.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
359                ip = request.getHeaders().getFirst("X-Real-IP");
360            }
361
362            // 兼容k8s集群获取ip
363            if (StrUtil.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
364                ip = request.getRemoteAddress().getAddress().getHostAddress();
365                if (LOCALHOST_IP1.equalsIgnoreCase(ip) || LOCALHOST_IP.equalsIgnoreCase(ip)) {
366                    //根据网卡取本机配置的IP
367                    InetAddress iNet = null;
368                    try {
369                        iNet = InetAddress.getLocalHost();
370                    } catch (UnknownHostException e) {
371                        log.error("getClientIp error: ", e);
372                    }
373                    ip = iNet.getHostAddress();
374                }
375            }
376        } catch (Exception e) {
377            log.error("IPUtils ERROR ", e);
378        }
379
380        // 使用代理,则获取第一个IP地址
381        if (StrUtil.isNotEmpty(ip) && ip.indexOf(IP_UTILS_FLAG) > 0) {
382            ip = ip.substring(0, ip.indexOf(IP_UTILS_FLAG));
383        }
384        return ip;
385    }
386
387    /**
388     * 获取访问域名
389     * @param exchange
390     * @return
391     */
392    public static String getServerName(ServerWebExchange exchange) {
393        // 获取请求
394        ServerHttpRequest request = exchange.getRequest();
395
396        return getServerName(request);
397    }
398
399    /**
400     * 获取访问域名
401     * @param request
402     * @return
403     */
404    public static String getServerName(ServerHttpRequest request) {
405        return request.getURI().getHost();
406    }
407}