作为网关,日志记录是必不可少的功能,可以在网关出增加requestId来查询整个请求链的调用执行情况等等。

打印请求日志

打印请求日志最重要的就是打印请求参数这些东西,不过RequestBody通常情况下在被读取一次之后就会失效,这样的话,下游的服务就不能正常获取到请求参数了。所以我们需要重写下请求体。

具体方法呢有很多,这里说一下我用的两种:

第一种

代码如下:

package com.lifengdi.gateway.filter;

import com.lifengdi.gateway.constant.HeaderConstant;
import com.lifengdi.gateway.constant.OrderedConstant;
import com.lifengdi.gateway.log.Log;
import com.lifengdi.gateway.log.LogHelper;
import com.lifengdi.gateway.utils.GenerateIdUtils;
import com.lifengdi.gateway.utils.IpUtils;
import io.netty.buffer.UnpooledByteBufAllocator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import java.net.URI;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer; /**
* 请求日志打印
*/
@Component
@Slf4j
public class RequestLogFilter implements GlobalFilter, Ordered { @Override
public int getOrder() {
return OrderedConstant.REQUEST_FILTER;
} @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
long startTime = System.currentTimeMillis();
try {
ServerHttpRequest request = exchange.getRequest();
// 设置X-Request-Id
AtomicReference<String> requestId = new AtomicReference<>(GenerateIdUtils.requestIdWithUUID());
Consumer<HttpHeaders> httpHeadersConsumer = httpHeaders -> {
String headerRequestId = request.getHeaders().getFirst(HeaderConstant.REQUEST_ID);
if (StringUtils.isBlank(headerRequestId)) {
httpHeaders.set(HeaderConstant.REQUEST_ID, requestId.get());
} else {
requestId.set(headerRequestId);
}
httpHeaders.set(HeaderConstant.START_TIME_KEY, String.valueOf(startTime));
};
ServerRequest serverRequest = ServerRequest.create(exchange,
HandlerStrategies.withDefaults().messageReaders());
URI requestUri = request.getURI();
String uriQuery = requestUri.getQuery();
String url = requestUri.getPath() + (StringUtils.isNotBlank(uriQuery) ? "?" + uriQuery : "");
HttpHeaders headers = request.getHeaders();
MediaType mediaType = headers.getContentType();
String method = request.getMethodValue().toUpperCase(); // 原始请求体
final AtomicReference<String> requestBody = new AtomicReference<>();
final AtomicBoolean newBody = new AtomicBoolean(false);
if (Objects.nonNull(mediaType) && LogHelper.isUploadFile(mediaType)) {
requestBody.set("上传文件");
} else {
if (method.equals("GET")) {
if (StringUtils.isNotBlank(uriQuery)) {
requestBody.set(uriQuery);
}
} else {
newBody.set(true);
}
}
final Log logDTO = new Log();
logDTO.setLevel(Log.LEVEL.INFO);
logDTO.setRequestUrl(url);
logDTO.setRequestBody(requestBody.get());
logDTO.setRequestMethod(method);
logDTO.setRequestId(requestId.get());
logDTO.setIp(IpUtils.getClientIp(request)); ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate().headers(httpHeadersConsumer).build();
ServerWebExchange build = exchange.mutate().request(serverHttpRequest).build();
return build.getSession().flatMap(webSession -> {
logDTO.setSessionId(webSession.getId());
if (newBody.get() && headers.getContentLength() > 0) {
Mono<String> bodyToMono = serverRequest.bodyToMono(String.class);
return bodyToMono.flatMap(reqBody -> {
logDTO.setRequestBody(reqBody);
// 重写原始请求
ServerHttpRequestDecorator requestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));
DataBuffer bodyDataBuffer = nettyDataBufferFactory.wrap(reqBody.getBytes());
return Flux.just(bodyDataBuffer);
}
};
return chain.filter(exchange.mutate()
.request(requestDecorator)
.build()).then(LogHelper.doRecord(logDTO));
});
} else {
return chain.filter(exchange).then(LogHelper.doRecord(logDTO));
}
}); } catch (Exception e) {
log.error("请求日志打印出现异常", e);
return chain.filter(exchange);
}
} }

上面的核心代码是:

// 重写原始请求
ServerHttpRequestDecorator requestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));
DataBuffer bodyDataBuffer = nettyDataBufferFactory.wrap(reqBody.getBytes());
return Flux.just(bodyDataBuffer);
}
};
return chain.filter(exchange.mutate()
.request(requestDecorator)
.build()).then(LogHelper.doRecord(logDTO));

如果不需要对session进行操作,可以直接调用这块就行。

关于请求时间,我这里采用的是将时间戳放进请求头中,等到打印日志的时候再从请求头中读取然后计算出时间。否则如果单独在某个filter中计算请求时间,会造成时间不太准确。当然这样时间也不是很准确,毕竟还有Spring本身的filter等业务逻辑,不过时间相差不是很大,大概十几毫秒的样子。

第二种

第二种就是自己缓存下请求体,读取的时候读取缓存内容。

代码如下:

package com.lifengdi.gateway.log;

import com.lifengdi.gateway.constant.HeaderConstant;
import com.lifengdi.gateway.utils.IpUtils;
import io.netty.buffer.UnpooledByteBufAllocator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import reactor.core.publisher.Flux;
import reactor.core.scheduler.Schedulers; import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Objects; /**
* 对ServerHttpRequest进行二次封装,解决requestBody只能读取一次的问题
* @author: Li Fengdi
* @date: 2020-03-17 18:02
*/
@Slf4j
public class CacheServerHttpRequestDecorator extends ServerHttpRequestDecorator {
private DataBuffer bodyDataBuffer;
private int getBufferTime = 0;
private byte[] bytes; public CacheServerHttpRequestDecorator(ServerHttpRequest delegate) {
super(delegate);
} @Override
public Flux<DataBuffer> getBody() {
if (getBufferTime == 0) {
getBufferTime++;
Flux<DataBuffer> flux = super.getBody();
return flux.publishOn(Schedulers.single())
.map(this::cache)
.doOnComplete(() -> trace(getDelegate())); } else {
return Flux.just(getBodyMore());
} } private DataBuffer getBodyMore() {
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));
bodyDataBuffer = nettyDataBufferFactory.wrap(bytes);
return bodyDataBuffer;
} private DataBuffer cache(DataBuffer buffer) {
try {
InputStream dataBuffer = buffer.asInputStream();
bytes = IOUtils.toByteArray(dataBuffer);
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));
bodyDataBuffer = nettyDataBufferFactory.wrap(bytes);
return bodyDataBuffer;
} catch (IOException e) {
e.printStackTrace();
}
return null;
} private void trace(ServerHttpRequest request) {
URI requestUri = request.getURI();
String uriQuery = requestUri.getQuery();
String url = requestUri.getPath() + (StringUtils.isNotBlank(uriQuery) ? "?" + uriQuery : "");
HttpHeaders headers = request.getHeaders();
MediaType mediaType = headers.getContentType();
String schema = requestUri.getScheme();
String method = request.getMethodValue().toUpperCase();
if ((!"http".equals(schema) && !"https".equals(schema))) {
return;
}
String reqBody = null;
if (Objects.nonNull(mediaType) && LogHelper.isUploadFile(mediaType)) {
reqBody = "上传文件";
} else {
if (method.equals("GET")) {
if (StringUtils.isNotBlank(uriQuery)) {
reqBody = uriQuery;
}
} else if (headers.getContentLength() > 0) {
reqBody = LogHelper.readRequestBody(request);
}
}
final Log logDTO = new Log();
logDTO.setLevel(Log.LEVEL.INFO);
logDTO.setRequestUrl(url);
logDTO.setRequestBody(reqBody);
logDTO.setRequestMethod(method);
logDTO.setRequestId(headers.getFirst(HeaderConstant.REQUEST_ID));
logDTO.setIp(IpUtils.getClientIp(request));
log.info(LogHelper.toJsonString(logDTO));
} }

filter这里就简单写下:

package com.lifengdi.gateway.filter;

import com.lifengdi.gateway.constant.OrderedConstant;
import com.lifengdi.gateway.log.CacheServerHttpRequestDecorator;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono; /**
* @author: Li Fengdi
* @date: 2020-03-17 18:17
*/
//@Component
public class LogFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
CacheServerHttpRequestDecorator cacheServerHttpRequestDecorator = new CacheServerHttpRequestDecorator(exchange.getRequest()); return chain.filter(exchange.mutate().request(cacheServerHttpRequestDecorator).build());
} @Override
public int getOrder() {
return OrderedConstant.LOGGING_FILTER;
}
}

工具类也贴下:

package com.lifengdi.gateway.log;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lifengdi.gateway.constant.HeaderConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import reactor.core.publisher.Mono; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference; @Slf4j
public class LogHelper { private final static ObjectMapper objectMapper = new ObjectMapper(); /**
* Log转JSON
* @param dto Log
* @return JSON字符串
*/
public static String toJsonString(@NonNull Log dto) {
try {
return objectMapper.writeValueAsString(dto);
} catch (JsonProcessingException e) {
log.error("Log转换JSON异常", e);
return null;
}
} /**
* 根据MediaType获取字符集,如果获取不到,则默认返回<tt>UTF_8</tt>
* @param mediaType MediaType
* @return Charset
*/
public static Charset getMediaTypeCharset(@Nullable MediaType mediaType) {
if (Objects.nonNull(mediaType) && mediaType.getCharset() != null) {
return mediaType.getCharset();
} else {
return StandardCharsets.UTF_8;
}
} /**
* 记录日志(后期可扩展为通过MQ将日志发送到ELK系统)
* @param dto Log
* @return Mono.empty()
*/
public static Mono<Void> doRecord(Log dto) {
log.info(toJsonString(dto));
return Mono.empty();
} /**
* 从HttpHeaders获取请求开始时间
* <p>
* 要求请求头中必须要有参数{@link HeaderConstant#START_TIME_KEY},否则将返回当前时间戳
* </p>
* @param headers HttpHeaders请求头
* @return 开始时间时间戳(Mills)
*/
public static long getStartTime(HttpHeaders headers) {
String startTimeStr = headers.getFirst(HeaderConstant.START_TIME_KEY);
return StringUtils.isNotBlank(startTimeStr) ? Long.parseLong(startTimeStr) : System.currentTimeMillis();
} /**
* 根据HttpHeaders请求头获取请求执行时间
* <p>
* 要求请求头中必须要有参数{@link HeaderConstant#START_TIME_KEY}
* </p>
* @param headers HttpHeaders请求头
* @return 请求执行时间
*/
public static long getHandleTime(HttpHeaders headers) {
String startTimeStr = headers.getFirst(HeaderConstant.START_TIME_KEY);
long startTime = StringUtils.isNotBlank(startTimeStr) ? Long.parseLong(startTimeStr) : System.currentTimeMillis();
return System.currentTimeMillis() - startTime;
} /**
* 读取请求体内容
* @param request ServerHttpRequest
* @return 请求体
*/
public static String readRequestBody(ServerHttpRequest request) {
HttpHeaders headers = request.getHeaders();
MediaType mediaType = headers.getContentType();
String method = request.getMethodValue().toUpperCase();
if (Objects.nonNull(mediaType) && mediaType.equals(MediaType.MULTIPART_FORM_DATA)) {
return "上传文件";
} else {
if (method.equals("GET")) {
if (!request.getQueryParams().isEmpty()) {
return request.getQueryParams().toString();
}
return null;
} else {
AtomicReference<String> bodyString = new AtomicReference<>();
request.getBody().subscribe(buffer -> {
byte[] bytes = new byte[buffer.readableByteCount()];
buffer.read(bytes);
DataBufferUtils.release(buffer);
bodyString.set(new String(bytes, getMediaTypeCharset(mediaType)));
});
return bodyString.get();
}
}
} /**
* 判断是否是上传文件
* @param mediaType MediaType
* @return Boolean
*/
public static boolean isUploadFile(@Nullable MediaType mediaType) {
if (Objects.isNull(mediaType)) {
return false;
}
return mediaType.equals(MediaType.MULTIPART_FORM_DATA)
|| mediaType.equals(MediaType.IMAGE_GIF)
|| mediaType.equals(MediaType.IMAGE_JPEG)
|| mediaType.equals(MediaType.IMAGE_PNG)
|| mediaType.equals(MediaType.MULTIPART_MIXED);
}
}

打印响应报文

响应报文需要在Spring重写了响应体之后才能获取到,所以对filter的执行顺序有要求,需要在

NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER之前执行。代码如下:

package com.lifengdi.gateway.filter;

import com.lifengdi.gateway.constant.HeaderConstant;
import com.lifengdi.gateway.constant.OrderedConstant;
import com.lifengdi.gateway.log.Log;
import com.lifengdi.gateway.log.LogHelper;
import com.lifengdi.gateway.utils.IpUtils;
import io.netty.buffer.UnpooledByteBufAllocator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import java.net.URI;
import java.nio.charset.Charset;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference; /**
* 请求响应日志打印
*/
@Component
@Slf4j
public class ResponseLogFilter implements GlobalFilter, Ordered { @Override
public int getOrder() {
return OrderedConstant.LOGGING_FILTER;
} @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
try {
ServerHttpRequest request = exchange.getRequest();
ServerRequest serverRequest = ServerRequest.create(exchange,
HandlerStrategies.withDefaults().messageReaders());
URI requestUri = request.getURI();
String uriQuery = requestUri.getQuery();
HttpHeaders headers = request.getHeaders();
MediaType mediaType = headers.getContentType();
String schema = requestUri.getScheme();
String method = request.getMethodValue().toUpperCase(); // 只记录http、https请求
if ((!"http".equals(schema) && !"https".equals(schema))) {
return chain.filter(exchange);
}
final AtomicReference<String> requestBody = new AtomicReference<>();// 原始请求体
// 排除流文件类型,比如上传的文件contentType.contains("multipart/form-data")
if (Objects.nonNull(mediaType) && LogHelper.isUploadFile(mediaType)) {
requestBody.set("上传文件");
return chain.filter(exchange);
} else {
if (method.equals("GET")) {
if (StringUtils.isNotBlank(uriQuery)) {
requestBody.set(uriQuery);
}
} else if (headers.getContentLength() > 0){
return serverRequest.bodyToMono(String.class).flatMap(reqBody -> {
requestBody.set(reqBody);
// 重写原始请求
ServerHttpRequestDecorator requestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(super.getHeaders());
return httpHeaders;
} @Override
public Flux<DataBuffer> getBody() {
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));
DataBuffer bodyDataBuffer = nettyDataBufferFactory.wrap(reqBody.getBytes());
return Flux.just(bodyDataBuffer);
// return Flux.just(reqBody).map(bx -> exchange.getRequest().bufferFactory().wrap(bx.getBytes()));
}
};
ServerHttpResponseDecorator responseDecorator = getServerHttpResponseDecorator(exchange,
requestBody);
return chain.filter(exchange.mutate()
.request(requestDecorator)
.response(responseDecorator)
.build());
});
}
ServerHttpResponseDecorator decoratedResponse = getServerHttpResponseDecorator(exchange,
requestBody);
return chain.filter(exchange.mutate()
.response(decoratedResponse)
.build());
} } catch (Exception e) {
log.error("请求响应日志打印出现异常", e);
return chain.filter(exchange);
} } private ServerHttpResponseDecorator getServerHttpResponseDecorator(ServerWebExchange exchange,
AtomicReference<String> requestBody) {
// 获取response的返回数据
ServerHttpResponse originalResponse = exchange.getResponse();
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
HttpStatus httpStatus = originalResponse.getStatusCode();
ServerHttpRequest request = exchange.getRequest();
URI requestUri = request.getURI();
String uriQuery = requestUri.getQuery();
String url = requestUri.getPath() + (StringUtils.isNotBlank(uriQuery) ? "?" + uriQuery : "");
HttpHeaders headers = request.getHeaders();
String method = request.getMethodValue().toUpperCase();
String requestId = headers.getFirst(HeaderConstant.REQUEST_ID); // 封装返回体
return new ServerHttpResponseDecorator(originalResponse) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<? extends DataBuffer> fluxBody = Flux.from(body);
return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
DataBuffer join = bufferFactory.join(dataBuffers);
byte[] content = new byte[join.readableByteCount()];
join.read(content);
DataBufferUtils.release(join);
Charset charset = LogHelper.getMediaTypeCharset(originalResponse.getHeaders().getContentType());
String responseBody = new String(content, charset); long handleTime = LogHelper.getHandleTime(headers);
Log logDTO = new Log(Log.TYPE.RESPONSE);
logDTO.setLevel(Log.LEVEL.INFO);
logDTO.setRequestUrl(url);
logDTO.setRequestBody(requestBody.get());
logDTO.setResponseBody(responseBody);
logDTO.setRequestMethod(method);
if (Objects.nonNull(httpStatus)) {
logDTO.setStatus(httpStatus.value());
}
logDTO.setHandleTime(handleTime);
logDTO.setRequestId(requestId);
logDTO.setIp(IpUtils.getClientIp(request));
exchange.getSession().subscribe(webSession -> {
logDTO.setSessionId(webSession.getId());
}); log.info("url:{},method:{},请求内容:{},响应内容:{},status:{},handleTime:{},requestId:{}",
url, method, requestBody.get(), responseBody, httpStatus,
handleTime, requestId);
log.info(LogHelper.toJsonString(logDTO));
return bufferFactory.wrap(content);
}));
}
return super.writeWith(body);
}
};
} }

代码已上传到git上,需要的可以去看看。

git代码地址:https://github.com/lifengdi/spring-cloud-gateway-demo

原文地址:https://www.lifengdi.com/archives/article/1778

从零搭建Spring Cloud Gateway网关(二)—— 打印请求响应日志的更多相关文章

  1. 从零搭建Spring Cloud Gateway网关(一)

    新建Spring Boot项目 怎么新建Spring Boot项目这里不再具体赘述,不会的可以翻看下之前的博客或者直接百度.这里直接贴出对应的pom文件. pom依赖如下: <?xml vers ...

  2. 从零搭建Spring Cloud Gateway网关(三)——报文结构转换

    背景 作为网关,有些时候可能报文的结构并不符合前端或者某些服务的需求,或者因为某些原因,其他服务修改报文结构特别麻烦.或者需要修改的地方特别多,这个时候就需要走网关单独转换一次. 实现 话不多说,直接 ...

  3. Spring Cloud gateway 网关服务二 断言、过滤器

    微服务当前这么火爆的程度,如果不能学会一种微服务框架技术.怎么能升职加薪,增加简历的筹码?spring cloud 和 Dubbo 需要单独学习.说没有时间?没有精力?要学俩个框架?而Spring C ...

  4. Spring Cloud gateway 网关四 动态路由

    微服务当前这么火爆的程度,如果不能学会一种微服务框架技术.怎么能升职加薪,增加简历的筹码?spring cloud 和 Dubbo 需要单独学习.说没有时间?没有精力?要学俩个框架?而Spring C ...

  5. Spring Cloud实战 | 第十一篇:Spring Cloud Gateway 网关实现对RESTful接口权限控制和按钮权限控制

    一. 前言 hi,大家好,这应该是农历年前的关于开源项目 的最后一篇文章了. 有来商城 是基于 Spring Cloud OAuth2 + Spring Cloud Gateway + JWT实现的统 ...

  6. .net core下,Ocelot网关与Spring Cloud Gateway网关的对比测试

    有感于 myzony 发布的 针对 Ocelot 网关的性能测试 ,并且公司下一步也需要对.net和java的应用做一定的整合,于是对Ocelot网关.Spring Cloud Gateway网关做个 ...

  7. 网关服务Spring Cloud Gateway(二)

    上一篇文章服务网关 Spring Cloud GateWay 初级篇,介绍了 Spring Cloud Gateway 的相关术语.技术原理,以及如何快速使用 Spring Cloud Gateway ...

  8. Spring Cloud gateway 网关服务 一

    之前我们介绍了 zuul网关服务,今天聊聊spring cloud gateway 作为spring cloud的亲儿子网关服务.很多的想法都是参照zuul,为了考虑zuul 迁移到gateway 提 ...

  9. Spring Cloud Gateway(二):Spring Cloud Gateway整合Eureka应用

    Spring Cloud Gateway 应用概述 下面的示例启动两个服务:gataway-server 和 user-service 都注册到注册中心 Eureka上,客户端请求后端服务[user- ...

随机推荐

  1. Tmux 速成教程:技巧和调整

    本文转自:http://blog.jobbole.com/87584/ 简介 有些开发者经常要使用终端控制台工作,导致最终打开了过多的标签页.如果你也是他们当中的一员,或者你正在实践结对编程,那么我推 ...

  2. php--ip的处理

    1.获取ip /**获取请求ip**/ function _get_request_ip(){ //strcasecmp 比较两个字符,不区分大小写.返回0,>0,<0. if(geten ...

  3. Python野生库

    https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted

  4. mysqldump免密备份方法

    注意:1.暂时只试验了root用户     2.暂时只试验了5.6和5.7两个版本 1.我用的root用户,先进入家目录 cd ~ 2.vim .my.cnf #在家目录添加该文件 [mysqldum ...

  5. OpenGL ES 学习笔记 - Overview - 小旋的博客

    移动端图形标准中,目前 OpenGL ES 仍然是比较通用的标准(Vulkan 则是新一代),这里新开一个系列用于记录学习 OpenGL ES 的历程,以便查阅理解. OverView OpenGL ...

  6. sycCMS PHP V1.0---呵呵呵呵呵

    闲的无聊,随便找了份代码看了看. //search.php 第17行 第49行 ...... $keyword=SafeRequest("keyword","post&q ...

  7. 微软亚洲研究院研究员获选IEEE Fellow 和ACM Distinguished Member

    ​ 年末将至,微软亚洲研究院喜讯连连.近日,IEEE(国际电气电子工程师学会)和ACM(美国计算机协会)先后公布了2017年度的院士名单(IEEE Fellow)和2016年度杰出会员名单(ACM D ...

  8. Spring Cloud Feign 组成和配置

    Feign的组成 接口 作用 默认值 Feign.Builder Feign的入口 Feign.Builder Client Feign底层用什么去请求 和Ribbon配合时:LoadBalancer ...

  9. Jstorm执行task报错windows CONFIG SET protected-mode no

    windows  CONFIG SET protected-mode no报错说redis受保护模式,redis使用的是Redis-x64-3.2.100,参考博文说是redis3.2之后加入的特性, ...

  10. CVE-2020-7245 CTFd v2.0.0 – v2.2.2漏洞分析复现

    CVE-2020-7245 CTFd v2.0.0 – v2.2.2漏洞分析复现 一.漏洞介绍 ​ 在 CTFd v2.0.0 - v2.2.2 的注册过程中,如果知道用户名并在 CTFd 实例上启用 ...