作为网关,日志记录是必不可少的功能,可以在网关出增加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. proxmox新版本使用了lxc容器,导致以前的vzlist命令无法使用,于是自己写了一个脚本来获取所有半虚拟化主机的信息状态

    #!/usr/bin/env python #encoding:utf-8 # desc:用来描述各个主机信息 import os #CTID NPROC STATUS IP_ADDR HOSTNAM ...

  2. 吴裕雄--天生自然HTML学习笔记:HTML 脚本

    JavaScript 使 HTML 页面具有更强的动态和交互性. <!DOCTYPE html> <html> <head> <meta charset=&q ...

  3. haproxy笔记之五:一个配置示例

    #--------------------------------------------------------------------- # Global settings #---------- ...

  4. 将js进行到底:node学习9

    node.js数据库篇--Mongoose ODM 介绍mongoose 几乎所有的语言都有原生数据库连接驱动,这个我们上一回已经了解了,比如java的jdbc,mysql-connector,但是实 ...

  5. 吴裕雄--天生自然 python开发学习笔记:pycharm无法使用ctrl+c/v复制粘贴的问题

    在使用pycharm的时候发现不能正常使用ctrl+c/v进行复制粘贴,也无法使用tab键对大段代码进行整体缩进.后来发现是因为安装了vim插件的问题,在setting里找到vim插件,取消勾选即可解 ...

  6. ArrayList与LinkList对比

    本文简要总结一下java中ArrayList与LinkedList的区别,这在面试中也是常常会问到的一个知识点. 先来看一下ArrayList和LinkedList的关系是怎样的: 从继承体系可以看到 ...

  7. 生鲜电商的两极战:巨头VS地头

    ​ ​ "九月蟹黄满,十月蟹肉香",螃蟹年年相似,总是美味无边,但购买渠道却随着互联网普及而变得愈发多样起来.此前,大闸蟹礼券风靡就是最佳代表之一.虽然也引发诸多问题,但消费者也越 ...

  8. Android中使用AsyncTask

    >##今天写作业用到了AnsyncTask,记录一下自己的使用情况 >###1.Android.os.AsyncTask类 >  1.AsyncTask类对线程间通讯进行了包装,我们 ...

  9. HTML常用标签的使用

    一.常见标签详解 1.<iframe>标签 HTML内联框架元素 <iframe> 表示嵌套的浏览上下文,有效地将另一个HTML页面嵌入到当前页面中.在HTML 4.01中,文 ...

  10. Vmware安装的linux系统开机黑屏,关闭显示虚拟机忙怎么怎么解决?

    在vm虚拟机中,可能会遇到打开一台主机直接黑屏,而且无法关闭,关闭会显示虚拟机繁忙这种情况,如下图: 一般是因为没有正常关机或者操作不当导致的   对此,解决办法一般有两种 第一种方法: 1.重启电脑 ...