作为网关,日志记录是必不可少的功能,可以在网关出增加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. [LC] 191. Number of 1 Bits

    Write a function that takes an unsigned integer and return the number of '1' bits it has (also known ...

  2. echart封装,前端简单路由,图表设置自动化

    https://github.com/cclient/EhartDemoSetByAngular 后端node.js 前端插件 echart,jquery,jqueryui,datapicker,an ...

  3. 将js进行到底:node学习1

    废话:自高中以来一直对编程充满激情,磨剑五年,如今要毕业了,我不想用我已经擅长的知识敷衍,而想以一个全新的领域去面向我的毕设--是时候学习一下node.js node.js基础 对于JavaScrip ...

  4. fastDFS 一二事 - 简易服务器搭建之--阿里云

    第一步:安装fastDFS依赖libevent工具包 yum -y install libevent 第二步:解压libfastcommon-1.0.7.tar.gz文件 tar -zvxf libf ...

  5. Python---1基础介绍

    因公司有自动化测试需求,开始自学python,跟着廖雪峰老师的教程,一边学习,一遍记笔记,将学习过程中,遇到的大大小小奇奇怪怪的问题,记录与此. 一.安装 Python是跨平台的,它可以运行在Wind ...

  6. ubuntu采用apt-get安装pure-ftpd

    apt-get install pure-ftpdcd /etc/pure-ftpd/auth #开启虚拟账户登陆;ln -s 60auth ../conf/PureDB 60auth #查看虚拟账户 ...

  7. 什么是SNAT

    SNAT是源地址转换,其作用是将ip数据包的源地址转换成另外一个地址,可能有人觉得奇怪,好好的为什么要进行ip地址转换啊,为了弄懂这个问题,我们要看一下局域网用户上公网的原理,假设内网主机A(192. ...

  8. Windows下python3登陆和操作linux服务器

    一.环境准备 python3远程连接需要用到pycrytodome和paramiko库,其中后者依赖前者,所以按照顺序来安装 1. 安装pycrytodome 1 pip install pycryt ...

  9. Vizceral小白入门

    Vizceral小白入门 接到一个任务,要求将N个program可视化,能一目了然查看当前爬虫状态.记得之前做测试时,一个queue service前端可视化效果不错,经询问是用vizceral开源框 ...

  10. SpringCloud入门(六): Hystrix监控

    Hystrix.stream 监控 <!--. 配置pom文件,引入actuator包--> <dependency> <groupId>org.springfra ...