{"id":20482934,"url":"https://github.com/lzhpo/panda-gateway","last_synced_at":"2025-04-13T14:34:18.624Z","repository":{"id":45190260,"uuid":"505035481","full_name":"lzhpo/panda-gateway","owner":"lzhpo","description":"手写 SpringCloud Gateway，从源码的角度了解它的核心思想以及各个功能的工作原理，从而方便于实现一套属于企业自定义的微服务网关。","archived":false,"fork":false,"pushed_at":"2024-04-29T16:51:29.000Z","size":548,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-27T05:34:16.955Z","etag":null,"topics":["gateway","microservices","spring-boot","spring-cloud-gateway"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lzhpo.png","metadata":{"files":{"readme":"README-EN.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-06-19T06:59:03.000Z","updated_at":"2024-08-27T12:57:50.000Z","dependencies_parsed_at":"2025-04-13T14:33:44.340Z","dependency_job_id":null,"html_url":"https://github.com/lzhpo/panda-gateway","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lzhpo%2Fpanda-gateway","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lzhpo%2Fpanda-gateway/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lzhpo%2Fpanda-gateway/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lzhpo%2Fpanda-gateway/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lzhpo","download_url":"https://codeload.github.com/lzhpo/panda-gateway/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248728643,"owners_count":21152263,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gateway","microservices","spring-boot","spring-cloud-gateway"],"created_at":"2024-11-15T16:15:15.826Z","updated_at":"2025-04-13T14:34:18.616Z","avatar_url":"https://github.com/lzhpo.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"![](https://img.shields.io/badge/JDK-1.8+-success.svg)\n![](https://maven-badges.herokuapp.com/maven-central/com.lzhpo/panda-gateway/badge.svg?color=blueviolet)\n![](https://img.shields.io/:license-Apache2-orange.svg)\n[![Style check](https://github.com/lzhpo/panda-gateway/actions/workflows/style-check.yml/badge.svg)](https://github.com/lzhpo/panda-gateway/actions/workflows/style-check.yml)\n[![Codacy Badge](https://app.codacy.com/project/badge/Grade/357eee58bffa4663b84a040b60b92f46)](https://www.codacy.com/gh/lzhpo/panda-gateway/dashboard?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=lzhpo/panda-gateway\u0026amp;utm_campaign=Badge_Grade)\n\n![](./docs/images/logo.png)\n\nEnglish | [中文](README-CN.md)\n\n## What's it?\n\n### Foreword\n\n- This project aims to handwrite SpringCloud Gateway.\n- Referring to the core idea of SpringCloud Gateway, I basically implemented all its functions in my own way.\n- You can use it to understand the internal working principle of SpringCloud Gateway and secondary development more quickly.\n- etc...\n\n### Features\n\n1. Powerful predicates and filters, easier to understand and expand.\n2. Route information storage can be dynamically switched to memory or redis.\n3. Supports servlet and webflux environments, as well as microservice mode and http/https mode.\n3. Support custom cross-domain configuration.\n3. etc...\n\n## How it works?\n\n![](docs/images/panda-gateway-logic.png)\n\n## Import dependencies\n\n### Servlet environment\n\n#### Maven\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.lzhpo\u003c/groupId\u003e\n  \u003cartifactId\u003epanda-gateway-servlet\u003c/artifactId\u003e\n  \u003cversion\u003e${latest-version}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n#### Gradle\n\n```groovy\nimplementation 'com.lzhpo:panda-gateway-servlet:${latest-version}'\n```\n\n### Webflux environment\n\n#### Maven\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.lzhpo\u003c/groupId\u003e\n  \u003cartifactId\u003epanda-gateway-webflux\u003c/artifactId\u003e\n  \u003cversion\u003e${latest-version}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n#### Gradle\n\n```groovy\nimplementation 'com.lzhpo:panda-gateway-webflux:${latest-version}'\n```\n\n## Route predicate\n\n\u003e Route predicates are used to evaluate whether the current request matches a certain route.\n\u003e\n\u003e The name format: `[PredicateName]`RoutePredicateFactory\n\n### Exist route predicate\n\n#### 1.`Path` route predicate\n\n\u003e e.g: If I want make request path is `/api/service-sample/**` or `/api/sample/**` forward to `lb://panda-service-sample`.\n\n```yaml\ngateway:\n  routes:\n    - id: panda-service-sample-01\n      uri: lb://panda-service-sample\n      predicates:\n        - name: Path\n          args:\n            paths: /api/service-sample/**, /api/sample/**\n```\n\n#### 2.`Weight` route predicate\n\n\u003e e.g: If I want to assign weight for route.\n\n```yaml\ngateway:\n  routes:\n    - id: panda-service-sample-01\n      uri: lb://panda-service-sample\n      order: 1\n      predicates:\n        - name: Weight\n          args:\n            group: service-sample\n            weight: 8\n    - id: panda-service-sample-02\n      uri: lb://panda-service-sample\n      order: 2\n      predicates:\n        - name: Weight\n          args:\n            group: service-sample\n            weight: 2\n```\n\n#### 3.`Parameter` route predicate\n\n\u003e e.g: If I want make request parameter have `nickName=Lewis` or `age=22` forward to `lb://panda-service-sample`.\n\n```yaml\ngateway:\n  routes:\n    - id: panda-service-sample-01\n      uri: lb://panda-service-sample\n      predicates:\n        - name: Parameter\n          args:\n            parameters:\n              nickName: Lewis\n              age: 20\n```\n\n**Notes**: The value support regex expression.\n\n#### 4.`ClientIp` route predicate\n\n\u003e e.g: If I want make request client ip is `192.168.200.111` or `192.168.200.112` forward to `lb://panda-service-sample`.\n\n```yaml\ngateway:\n  routes:\n    - id: panda-service-sample-01\n      uri: lb://panda-service-sample\n      predicates:\n        - name: ClientIp\n          args:\n            clientIps: 192.168.200.111, 192.168.200.112\n```\n\n#### 5.`Cookie` route predicate\n\n\u003e e.g: If I want make request cookie is `deviceId=123456` or `age=22` forward to `lb://panda-service-sample`.\n\n```yaml\ngateway:\n  routes:\n    - id: panda-service-sample-01\n      uri: lb://panda-service-sample\n      predicates:\n        - name: Cookie\n          args:\n            cookies:\n              deviceId: 123456\n              age: 22\n```\n\n**Notes**: The value support regex expression.\n\n#### 6.`Header` route predicate\n\n\u003e e.g: If I want make request cookie is `X-B3-TraceId=123456` or `X-B3-SpanId=123456` forward to `lb://panda-service-sample`.\n\n```yaml\ngateway:\n  routes:\n    - id: panda-service-sample-01\n      uri: lb://panda-service-sample\n      predicates:\n        - name: Header\n          args:\n            headers:\n              X-B3-TraceId: 123456\n              X-B3-SpanId: 123456\n```\n\n**Notes**: The value support regex expression.\n\n#### 7.`Method` route predicate\n\n\u003e e.g: If I want make request method is `PUT` or `PATCH` forward to `lb://panda-service-sample`.\n\n```yaml\ngateway:\n  routes:\n    - id: panda-service-sample-01\n      uri: lb://panda-service-sample\n      predicates:\n        - name: Method\n          args:\n            methods: PUT, PATCH\n```\n\n#### 8.`After` route predicate\n\n\u003e e.g: If I want make request time is after `2030-06-30T01:29:48.0875598+08:00[Asia/Shanghai]` forward to `lb://panda-service-sample`.\n\n```yaml\ngateway:\n  routes:\n    - id: panda-service-sample-01\n      uri: lb://panda-service-sample\n      predicates:\n        - name: After\n          args:\n            time: 2030-06-30T01:29:48.0875598+08:00[Asia/Shanghai]\n```\n\n**Notes**: The value type is `ZonedDateTime`.\n\nYou can easy to get the format value:\n\n```java\nZonedDateTime.now().format(DateTimeFormatter.ISO_ZONED_DATE_TIME)\n```\n\n#### 9.`Before` route predicate\n\n\u003e e.g: If I want make request time is before `2015-06-30T01:29:48.0875598+08:00[Asia/Shanghai]` forward to `lb://panda-service-sample`.\n\n```yaml\ngateway:\n  routes:\n    - id: panda-service-sample-01\n      uri: lb://panda-service-sample\n      predicates:\n        - name: Before\n          args:\n            time: 2015-06-30T01:29:48.0875598+08:00[Asia/Shanghai]\n```\n\n**Notes**: The value type is `ZonedDateTime`.\n\nYou can easy to get the format value:\n\n```java\nZonedDateTime.now().format(DateTimeFormatter.ISO_ZONED_DATE_TIME)\n```\n\n#### 10.`Between` route predicate\n\n\u003e e.g: If you want make the request time is \n\u003e\n\u003e ```java\n\u003e start time: 2012-06-30T01:29:48.0875598+08:00[Asia/Shanghai]\n\u003e end time: 2018-06-30T01:29:48.0875598+08:00[Asia/Shanghai]\n\u003e ```\n\u003e\n\u003e or\n\u003e\n\u003e ```java\n\u003e start time: 2020-10-01T01:29:48.0875598+08:00[Asia/Shanghai]\n\u003e end time: 2030-10-01T01:29:48.0875598+08:00[Asia/Shanghai]\n\u003e ```\n\u003e\n\u003e forward to `lb://panda-service-sample`。\n\n```yaml\ngateway:\n  routes:\n    - id: panda-service-sample-01\n      uri: lb://panda-service-sample\n      predicates:\n        - name: Between\n          args:\n            times:\n              - start: 2012-06-30T01:29:48.0875598+08:00[Asia/Shanghai]\n                end: 2018-06-30T01:29:48.0875598+08:00[Asia/Shanghai]\n              - start: 2020-10-01T01:29:48.0875598+08:00[Asia/Shanghai]\n                end: 2030-10-01T01:29:48.0875598+08:00[Asia/Shanghai]\n```\n\n**Notes**: \n\n1. Support many time pair.\n\n2. The end value type is `ZonedDateTime`.\n\n   You can easy to get the format value:\n\n   ```java\n   ZonedDateTime.now().format(DateTimeFormatter.ISO_ZONED_DATE_TIME)\n   ```\n\n### Custom route predicate relation\n\n\u003e You can define the relation of routing predicates. By setting `gateway.routes[x].metadata.predicate-relation`, it can be set to `AND` (match all predicates) or `OR` (match any predicate), case insensitive, the default is `AND` (match all predicates).\n\n#### `AND`\n\n\u003e This route is used when the request header have `X-B3-TraceId=123456` **and** the request parameter have `nickName=Lewis`.\n\n```yaml\ngateway:\n  routes:\n    - id: panda-service-sample-01\n      uri: lb://panda-service-sample\n      order: 1\n      metadata:\n        predicate-relation: and\n      predicates:\n        - name: Header\n          args:\n            headers:\n              X-B3-TraceId: 123456\n        - name: Parameter\n          args:\n            parameters:\n              nickName: Lewis \n```\n\n#### `OR`\n\n\u003e This route is used when the request header have `X-B3-TraceId=123456` **or** the request parameter have `nickName=Lewis`.\n\n```yaml\ngateway:\n  routes:\n    - id: panda-service-sample-01\n      uri: lb://panda-service-sample\n      order: 1\n      metadata:\n        predicate-relation: or\n      predicates:\n        - name: Header\n          args:\n            headers:\n              X-B3-TraceId: 123456\n        - name: Parameter\n          args:\n            parameters:\n              nickName: Lewis \n```\n\n### How to implement route predicate?\n\nI will use `After` route predicate as example to tell you how to implement it.\n\n#### Servlet environment\n\n```java\n@Component\npublic class AfterRoutePredicateFactory\n    extends AbstractRoutePredicateFactory\u003cAfterRoutePredicateFactory.Config\u003e {\n\n  public AfterRoutePredicateFactory() {\n    super(Config.class);\n  }\n\n  @Override\n  public RoutePredicate create(Config config) {\n    return request -\u003e {\n      ZonedDateTime nowTime = ZonedDateTime.now();\n      ZonedDateTime afterTime = config.getTime();\n      return nowTime.isAfter(afterTime);\n    };\n  }\n\n  @Data\n  @Validated\n  public static class Config {\n\n    @NotNull \n    private ZonedDateTime time;\n  }\n}\n```\n\n#### Webflux environment\n\n```java\n@Component\npublic class AfterRoutePredicateFactory\n    extends AbstractRoutePredicateFactory\u003cAfterRoutePredicateFactory.Config\u003e {\n\n  public AfterRoutePredicateFactory() {\n    super(Config.class);\n  }\n\n  @Override\n  public RoutePredicate create(Config config) {\n    return request -\u003e {\n      ZonedDateTime nowTime = ZonedDateTime.now();\n      ZonedDateTime afterTime = config.getTime();\n      return nowTime.isAfter(afterTime);\n    };\n  }\n\n  @Data\n  @Validated\n  public static class Config {\n\n    @NotNull \n    private ZonedDateTime time;\n  }\n}\n```\n\n#### Use the `After` route predicate\n\n```yaml\ngateway:\n  routes:\n    - id: panda-service-sample-01\n      uri: lb://panda-service-sample\n      predicates:\n        - name: After\n          args:\n            time: 2030-06-30T01:29:48.0875598+08:00[Asia/Shanghai]\n```\n\n## Route filter\n\n\u003e The name format: `[FilterName]`RouteFilterFactory\n\u003e\n\u003e Route filter just apply current route.\n\n### Exist route filter\n\n#### 1.`AddRequestHeader` route filter\n\n\u003e e.g: If you want add request headers.\n\n```yaml\ngateway:\n  routes:\n    - id: panda-service-sample-01\n      uri: lb://panda-service-sample\n      filters:\n        - name: AddRequestHeader\n          args:\n            headers:\n              name: Lewis\n              age: 123\n```\n\n#### 2.`AddRequestParameter` route filter\n\n\u003e e.g: If you want add request parameters.\n\n```yaml\ngateway:\n  routes:\n    - id: panda-service-sample-01\n      uri: lb://panda-service-sample\n      filters:\n        - name: AddRequestParameter\n          args:\n            parameters:\n              userId: 123\n```\n\n#### 3.`AddResponseHeader` route filter\n\n\u003e e.g: If you want add response headers.\n\n```yaml\ngateway:\n  routes:\n    - id: panda-service-sample-01\n      uri: lb://panda-service-sample\n      filters:\n        - name: AddResponseHeader\n          args:\n            headers:\n              name: Jack\n              age: 20\n```\n\n#### 4.`RemoveRequestHeader` route filter\n\n\u003e e.g: If you want remove request headers.\n\n```yaml\ngateway:\n  routes:\n    - id: panda-service-sample-01\n      uri: lb://panda-service-sample\n      filters:\n        - name: RemoveRequestHeader\n          args:\n            headers:\n              X-B3-TraceId: 123\n              X-B3-SpanId: 456\n```\n\n**Notes**: the value support regex expression.\n\n#### 5.`RemoveRequestParameter` route filter\n\n\u003e e.g: If you want remove request parameters.\n\n```yaml\ngateway:\n  routes:\n    - id: panda-service-sample-01\n      uri: lb://panda-service-sample\n      filters:\n        - name: RemoveRequestParameter\n          args:\n            parameters:\n              traceId: 123\n              spanId: 456\n```\n\n**Notes**: the value support regex expression.\n\n#### 6.`RemoveResponseHeader` route filter\n\n\u003e e.g: If you want remove response headers.\n\n```yaml\ngateway:\n  routes:\n    - id: panda-service-sample-01\n      uri: lb://panda-service-sample\n      filters:\n        - name: RemoveResponseHeader\n          args:\n            headers:\n              country: China\n              city: Guangzhou\n```\n\n**Notes**: the value support regex expression.\n\n#### 7.`RateLimiter` route filter\n\n\u003e e.g: If you want limit the current of the request.\n\n```yaml\ngateway:\n  routes:\n    - id: panda-service-sample-01\n      uri: lb://panda-service-sample\n      filters:\n        - name: RateLimiter\n          args:\n            includeHeaders: true\n            replenishRate: 1\n            burstCapacity: 1\n            requestedTokens: 1\n            limitedCode: 429\n            limitedMessage: \"Request too frequent\"\n            keyResolver: \"#{@clientIpKeyResolver}\"\n            rateLimiter: \"#{@redisRateLimiter}\"\n```\n\n### How to implement route filter?\n\nI will use `AddResponseHeader` route filter to tell you how to implement it.\n\n#### Servlet environment\n\n```java\n@Component\npublic class AddResponseHeaderRouteFilterFactory\n    extends AbstractRouteFilterFactory\u003cAddResponseHeaderRouteFilterFactory.Config\u003e\n    implements Ordered {\n\n  public AddResponseHeaderRouteFilterFactory() {\n    super(AddResponseHeaderRouteFilterFactory.Config.class);\n  }\n\n  @Override\n  public RouteFilter create(Config config) {\n    return (request, response, chain) -\u003e {\n      Map\u003cString, String\u003e configHeaders = config.getHeaders();\n      configHeaders.forEach(response::addHeader);\n      chain.doFilter(request, response);\n    };\n  }\n\n  @Data\n  @Validated\n  public static class Config {\n\n    @NotEmpty \n    private Map\u003cString, String\u003e headers;\n  }\n\n  @Override\n  public int getOrder() {\n    return Ordered.LOWEST_PRECEDENCE;\n  }\n}\n```\n\n#### Webflux environment\n\n```java\n@Component\npublic class AddResponseHeaderRouteFilterFactory\n    extends AbstractRouteFilterFactory\u003cAddResponseHeaderRouteFilterFactory.Config\u003e\n    implements Ordered {\n\n  public AddResponseHeaderRouteFilterFactory() {\n    super(Config.class);\n  }\n\n  @Override\n  public RouteFilter create(Config config) {\n    return (exchange, filterChain) -\u003e {\n      Map\u003cString, String\u003e configHeaders = config.getHeaders();\n      ServerHttpResponse response = exchange.getResponse();\n      HttpHeaders respHeaders = response.getHeaders();\n      configHeaders.forEach(respHeaders::remove);\n      return filterChain.filter(exchange);\n    };\n  }\n\n  @Data\n  @Validated\n  public static class Config {\n\n    @NotEmpty \n    private Map\u003cString, String\u003e headers;\n  }\n\n  @Override\n  public int getOrder() {\n    return Ordered.LOWEST_PRECEDENCE;\n  }\n}\n```\n\n#### Use the `AddResponseHeader` route filter\n\n```yaml\ngateway:\n  routes:\n    - id: panda-service-sample-01\n      uri: lb://panda-service-sample\n      filters:\n        - name: AddResponseHeader\n          args:\n            headers:\n              name: Jack\n              age: 20\n```\n\n## Global filter\n\n\u003e The global filter will apply all routes, and the global filter not have anything name constraint.\n\n### How to implement global filter?\n\n#### Servlet environment\n\n```java\n@Component\npublic class ResponseGlobalFilter implements GlobalFilter {\n\n  @Override\n  public void filter(\n      HttpServletRequest request, HttpServletResponse response, RouteFilterChain chain) {\n    response.addHeader(\"country\", \"China\");\n    response.addHeader(\"city\", \"Guangzhou\");\n    chain.doFilter(request, response);\n  }\n}\n```\n\n#### Webflux environment\n\n```java\n@Component\npublic class ResponseGlobalFilter implements GlobalFilter {\n\n  @Override\n  public Mono\u003cVoid\u003e filter(ServerWebExchange exchange, DefaultRouteFilterChain filterChain) {\n    ServerHttpResponse response = exchange.getResponse();\n    HttpHeaders headers = response.getHeaders();\n    headers.add(\"country\", \"China\");\n    headers.add(\"city\", \"Guangzhou\");\n    return filterChain.filter(exchange);\n  }\n}\n```\n\n## Cross-domain configuration\n\n\u003e  e.g: If I want allow any cross-domain.\n\n```yaml\ngateway:\n  cross-configurations:\n    '[/**]':\n      allowed-headers: \"*\"\n      allowed-methods: \"*\"\n      allowed-origins: \"*\"\n```\n\n## Unified custom exception response format\n\n### Servlet environment\n\n#### method1 - extends `DefaultErrorAttributes`\n\n```java\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\nimport org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;\nimport org.springframework.boot.web.error.ErrorAttributeOptions;\nimport org.springframework.boot.web.servlet.error.DefaultErrorAttributes;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.context.request.WebRequest;\n\n/**\n * Customize error response data.\n *\n * @see ErrorMvcAutoConfiguration#errorAttributes()\n * @author lzhpo\n */\n@Component\npublic class GatewayErrorAttributes extends DefaultErrorAttributes {\n\n  @Override\n  public Map\u003cString, Object\u003e getErrorAttributes(\n      WebRequest webRequest, ErrorAttributeOptions options) {\n    Map\u003cString, Object\u003e errors = super.getErrorAttributes(webRequest, options);\n    Map\u003cString, Object\u003e errorAttributes = new HashMap\u003c\u003e(4);\n    errorAttributes.put(\"success\", false);\n    errorAttributes.put(\"code\", errors.getOrDefault(\"status\", 500));\n    errorAttributes.put(\"message\", getErrorMessage(errors));\n    errorAttributes.put(\"data\", null);\n    return errorAttributes;\n  }\n\n  /**\n   * Get an error message.\n   *\n   * @param errors error attributes\n   * @return error message\n   */\n  private Object getErrorMessage(Map\u003cString, Object\u003e errors) {\n    return Optional.ofNullable(errors.get(\"message\"))\n        .orElseGet(() -\u003e errors.getOrDefault(\"error\", \"Internal Server Error\"));\n  }\n}\n```\n\nreturn format (example): \n\n```json\n{\n    \"code\": 504,\n    \"message\": \"Gateway Timeout\",\n    \"data\": null,\n    \"success\": false\n}\n```\n\n### Webflux environment\n\n#### method1 - extends `DefaultErrorWebExceptionHandler`\n\n```java\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\nimport org.springframework.beans.factory.ObjectProvider;\nimport org.springframework.boot.autoconfigure.web.ServerProperties;\nimport org.springframework.boot.autoconfigure.web.WebProperties;\nimport org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;\nimport org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration;\nimport org.springframework.boot.web.reactive.error.DefaultErrorAttributes;\nimport org.springframework.boot.web.reactive.error.ErrorAttributes;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.http.MediaType;\nimport org.springframework.http.codec.ServerCodecConfigurer;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.reactive.function.BodyInserters;\nimport org.springframework.web.reactive.function.server.ServerRequest;\nimport org.springframework.web.reactive.function.server.ServerResponse;\nimport org.springframework.web.reactive.result.view.ViewResolver;\nimport reactor.core.publisher.Mono;\n\n/**\n * Customize error response.\n *\n * @see DefaultErrorAttributes\n * @see ErrorWebFluxAutoConfiguration#errorWebExceptionHandler\n * @author lzhpo\n */\n@Order(-2)\n@Component\npublic class GatewayErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {\n\n  public GatewayErrorWebExceptionHandler(\n      WebProperties webProperties,\n      ErrorAttributes errorAttributes,\n      ServerProperties serverProperties,\n      ApplicationContext applicationContext,\n      ObjectProvider\u003cViewResolver\u003e viewResolvers,\n      ServerCodecConfigurer serverCodecConfigurer) {\n    super(\n        errorAttributes,\n        webProperties.getResources(),\n        serverProperties.getError(),\n        applicationContext);\n    setViewResolvers(viewResolvers.orderedStream().collect(Collectors.toList()));\n    setMessageWriters(serverCodecConfigurer.getWriters());\n    setMessageReaders(serverCodecConfigurer.getReaders());\n  }\n\n  @Override\n  public Mono\u003cServerResponse\u003e renderErrorResponse(ServerRequest request) {\n    Map\u003cString, Object\u003e errors =\n        getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));\n    int status = (int) errors.getOrDefault(\"status\", 500);\n\n    Map\u003cString, Object\u003e errorAttributes = new HashMap\u003c\u003e(4);\n    errorAttributes.put(\"success\", false);\n    errorAttributes.put(\"code\", status);\n    errorAttributes.put(\"message\", getErrorMessage(errors));\n    errorAttributes.put(\"data\", null);\n\n    return ServerResponse.status(status)\n        .contentType(MediaType.APPLICATION_JSON)\n        .body(BodyInserters.fromValue(errorAttributes));\n  }\n\n  /**\n   * Get an error message.\n   *\n   * @param errors error attributes\n   * @return error message\n   */\n  private Object getErrorMessage(Map\u003cString, Object\u003e errors) {\n    return Optional.ofNullable(errors.get(\"message\"))\n        .orElseGet(() -\u003e errors.getOrDefault(\"error\", \"Internal Server Error\"));\n  }\n}\n```\n\nreturn format (example): \n\n```json\n{\n    \"code\": 504,\n    \"message\": \"Gateway Timeout\",\n    \"data\": null,\n    \"success\": false\n}\n```\n\n#### method2 - extends `DefaultErrorAttributes`\n\nUsing this method, the `errorAttributes` returned by rewriting in the webflux environment needs to have `status`, otherwise, will throw NullPointerException, which does not happen in the servlet environment.\n\n```java\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\nimport org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;\nimport org.springframework.boot.web.error.ErrorAttributeOptions;\nimport org.springframework.boot.web.reactive.error.DefaultErrorAttributes;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.reactive.function.server.ServerRequest;\n\n/**\n * Customize error response data.\n *\n * @author lzhpo\n */\n@Component\npublic class GatewayErrorAttributes extends DefaultErrorAttributes {\n\n  /**\n   * Notes: errorAttributes must containsKey \"status\", otherwise, will throw NullPointerException\n   *\n   * \u003cpre\u003e{@code\n   * \tprotected Mono\u003cServerResponse\u003e renderErrorResponse(ServerRequest request) {\n   * \t\tMap\u003cString, Object\u003e error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));\n   * \t\treturn ServerResponse.status(getHttpStatus(error)).contentType(MediaType.APPLICATION_JSON)\n   * \t\t\t\t.body(BodyInserters.fromValue(error));\n   *  }\n   *\n   * \tprotected int getHttpStatus(Map\u003cString, Object\u003e errorAttributes) {\n   * \t\treturn (int) errorAttributes.get(\"status\");\n   *  }\n   * }\u003c/pre\u003e\n   *\n   * @see DefaultErrorWebExceptionHandler#renderErrorResponse\n   * @see DefaultErrorWebExceptionHandler#getHttpStatus\n   * @param request the source request\n   * @param options options for error attribute contents\n   * @return error attributes\n   */\n  @Override\n  public Map\u003cString, Object\u003e getErrorAttributes(\n      ServerRequest request, ErrorAttributeOptions options) {\n    Map\u003cString, Object\u003e errors = super.getErrorAttributes(request, options);\n    Map\u003cString, Object\u003e errorAttributes = new HashMap\u003c\u003e(4);\n    errorAttributes.put(\"success\", false);\n    errorAttributes.put(\"status\", errors.getOrDefault(\"status\", 500));\n    errorAttributes.put(\"message\", getErrorMessage(errors));\n    errorAttributes.put(\"data\", null);\n    return errorAttributes;\n  }\n\n  /**\n   * Get an error message.\n   *\n   * @param errors error attributes\n   * @return error message\n   */\n  private Object getErrorMessage(Map\u003cString, Object\u003e errors) {\n    return Optional.ofNullable(errors.get(\"message\"))\n        .orElseGet(() -\u003e errors.getOrDefault(\"error\", \"Internal Server Error\"));\n  }\n}\n```\n\nDetails can be seen: \n\n```java\n// org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler#renderErrorResponse\nprotected Mono\u003cServerResponse\u003e renderErrorResponse(ServerRequest request) {\n    Map\u003cString, Object\u003e error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));\n    return ServerResponse.status(getHttpStatus(error)).contentType(MediaType.APPLICATION_JSON)\n        .body(BodyInserters.fromValue(error));\n}\n\n// org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler#getHttpStatus\nprotected int getHttpStatus(Map\u003cString, Object\u003e errorAttributes) {\n    return (int) errorAttributes.get(\"status\");\n}\n```\n\nreturn format (example): \n\n```json\n{\n    \"status\": 504,\n    \"message\": \"Gateway Timeout\",\n    \"data\": null,\n    \"success\": false\n}\n```\n\n## Actuator endpoint API\n\nWe need exposure `gateway` endpoint if we want to do about gateway something.\n\n```java\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: gateway\n```\n\n### 1.Get configuration of all route\n\n```js\nGET /actuator/gateway/routes\n```\n\n### 2.Get route configuration by routeId\n\n```js\nGET /actuator/gateway/routes/${routeId}\n```\n\n### 3.Get predicate class name of all route\n\n```js\nGET /actuator/gateway/routes/predicates\n```\n\n### 4.Get predicate class name by routeId\n\n```js\nGET /actuator/gateway/routes/${routeId}/predicates\n```\n\n### 5.Get filter class name of all route\n\n```js\nGET /actuator/gateway/routes/filters\n```\n\n### 6.Get filter class name by routeId\n\n```js\nGET /actuator/gateway/routes/${routeId}/filters\n```\n\n### 7.Get global filter class name of all route\n\n```js\nGET /actuator/gateway/routes/global-filters\n```\n\n### 8.Refresh route\n\n```js\nPOST /actuator/gateway/routes/refresh\n```\n\n\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flzhpo%2Fpanda-gateway","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flzhpo%2Fpanda-gateway","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flzhpo%2Fpanda-gateway/lists"}