spring boot 配置网关时404错误_Kong中使用grpcweb插件代理grpc服务时遇到的坑
我们项目中使用的API Gateway是Kong,在代理grpc服务的时候,我们需要将此grpc服务以http/https协议提供给前端访问,因此需要用到Kong提供的grpc-web插件来帮忙将http/https的请求代理到后端的grpc服务上,在kong-plugin-grpc-web的官网上提供了配置实例,但按照此实例配置没法代理成功,而且基本网上都没有找到其他demo和资料,解决过程很简单,就是根据错误信息查看源码,分析出实际项目中该如何使用grpc-web插件来配置grpc服务。
关于Kong
Kong是一款基于Nginx_Lua模块写的高可用,易扩展由Mashape公司开源的API Gateway项目。由于Kong是基于Nginx的,所以可以水平扩展多个Kong服务器,通过前置的负载均衡配置把请求均匀地分发到各个Server,来应对大批量的网络请求。Kong采用插件机制进行功能定制,插件集(可以是0或n个)在API请求响应循环的生命周期中被执行。插件使用Lua编写,目前已有几个基础功能:HTTP基本认证、密钥认证、CORS( Cross-origin Resource Sharing,跨域资源共享)、TCP、UDP、文件日志、API请求限流、请求转发以及nginx监控。这篇文章中我们会用到另一个开源的插件,官网地址
https://github.com/Kong/kong-plugin-grpc-web
关于Kong gRPC-Web插件
A Kong plugin to allow access to a gRPC service via the gRPC-Web protocol. Primarily, this means JS browser apps using the gRPC-Web library.
A service that presents a gRPC API can be used by clients written in many languages, but the network specifications are oriented primarily to connections within a datacenter. In order to expose the API to the Internet, and to be called from brower-based JS apps, gRPC-Web was developed.
This plugin translates requests and responses between gRPC-Web and "real" gRPC. Supports both HTTP/1.1 and HTTP/2, over plaintext (HTTP) and TLS (HTTPS) connections.
这是来自官网的描述,简单的说就是开发者可以使用任何语言开发gRPC服务,前端js程序需要通过gRPC-Web协议来访问gRPC服务,使用此插件可以实现使用HTTP REST请求来请求后端的gRPC服务,请求和返回数据是json格式。
Springboot开发grpc服务
为了测试gRPC代理,使用springboot开发一个简单的gRPC服务。
第一步,定义proto文件,并放在src/main/proto目录下,命名为HelloWorld.proto
syntax = "proto3";option java_multiple_files = true;package com.example.grpc.helloworld;message Person {string first_name = 1;string last_name = 2;}message Greeting {string message = 1;}service HelloWorldService {rpc sayHello (Person) returns (Greeting);}
第二步,设置Maven
注意以下几个地方,spring-boot-starter-web,protobuf-maven-plugin和build-helper-maven-plugin。spring-boot-starter-web将自动的使用内置的tomcat来部署grpc服务。protobuf-maven-plugin是用来根据定义的proto文件来生成grpc-based的java代码。build-helper-maven-plugin此插件是用来设置将产生的java source code作为编译的一部分,也能帮助IntellJ工具找到源码,否则虽然编译能成功,但IntellJ会出现很多的红线提示错误,不太友好,而且也不能F3定位到源码的位置。
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0modelVersion><parent><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-parentartifactId><version>2.3.1.RELEASEversion><relativePath/> parent><groupId>com.example.grpcgroupId><artifactId>demoartifactId><version>0.0.1-SNAPSHOTversion><name>demoname><description>Demo project for Spring Bootdescription><properties><project.build.sourceEncoding>UTF-8project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding><java.version>1.8java.version><grpc-spring-boot-starter.version>3.0.0grpc-spring-boot-starter.version><os-maven-plugin.version>1.6.1os-maven-plugin.version><protobuf-maven-plugin.version>0.6.1protobuf-maven-plugin.version>properties><dependencies><dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-webartifactId>dependency><dependency><groupId>io.github.lognetgroupId><artifactId>grpc-spring-boot-starterartifactId><version>${grpc-spring-boot-starter.version}version>dependency><dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-testartifactId><scope>testscope>dependency><dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-log4j2artifactId>dependency><dependency><groupId>org.projectlombokgroupId><artifactId>lombokartifactId>dependency>dependencies><build><extensions><extension><groupId>kr.motd.mavengroupId><artifactId>os-maven-pluginartifactId><version>${os-maven-plugin.version}version>extension>extensions><plugins><plugin><groupId>org.springframework.bootgroupId><artifactId>spring-boot-maven-pluginartifactId>plugin><plugin><groupId>org.xolstice.maven.pluginsgroupId><artifactId>protobuf-maven-pluginartifactId><version>${protobuf-maven-plugin.version}version><configuration><protocArtifact>com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier}protocArtifact><pluginId>grpc-javapluginId><pluginArtifact>io.grpc:protoc-gen-grpc-java:1.16.1:exe:${os.detected.classifier}pluginArtifact>configuration><executions><execution><goals><goal>compilegoal><goal>compile-customgoal>goals>execution>executions>plugin><plugin><groupId>org.codehaus.mojogroupId><artifactId>build-helper-maven-pluginartifactId><version>1.4version><executions><execution><id>testid><phase>generate-sourcesphase><goals><goal>add-sourcegoal>goals><configuration><sources><source>${basedir}/target/generated-sourcessource>sources>configuration>execution>executions>plugin>plugins>build>project>
第三步,实现grpc服务
服务实现代码很简单,如下:
@GRpcService@Slf4jpublic class HelloWorldServiceImplextends HelloWorldServiceGrpc.HelloWorldServiceImplBase {@Overridepublic void sayHello(Person request, StreamObserver responseObserver) { log.info("server received {}", request); String message = "Hello " + request.getFirstName() + " " + request.getLastName() + "!"; Greeting greeting = Greeting.newBuilder().setMessage(message).build(); log.info("server responded {}", greeting); responseObserver.onNext(greeting); responseObserver.onCompleted(); }}
使用springboot运行此项目,将默认启动6565端口发布此grpc服务。下面我们就使用grpc-web 插件部署此服务
安装KONG
第一步,创建Docker网络
docker network create kong-net
第二步,安装Postgresql或者Cassandra
安装Cassandra作为存储
docker run -d --name kong-database \--network=kong-net \ -p 9042:9042 \ cassandra:3
或者安装Postgresql作为存储
docker run -d --name kong-database \--network=kong-net \ -p 5432:5432 \ -e "POSTGRES_USER=kong" \ -e "POSTGRES_DB=kong" \ -e "POSTGRES_PASSWORD=kong" \ postgres:9.6
第三步,初始kong数据
docker run --rm \--network=kong-net \ -e "KONG_DATABASE=postgres" \ -e "KONG_PG_HOST=kong-database" \ -e "KONG_PG_USER=kong" \ -e "KONG_PG_PASSWORD=kong" \ -e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \ kong:latest kong migrations bootstrap
第四步,启动kong
docker run -d --name kong \ --network=kong-net \ -e "KONG_DATABASE=postgres" \ -e "KONG_PG_HOST=kong-database" \ -e "KONG_PG_USER=kong" \ -e "KONG_PG_PASSWORD=kong" \ -e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \ -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \ -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \ -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \ -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \ -e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" \ -p 8000:8000 \ -p 8443:8443 \ -p 127.0.0.1:8001:8001 \ -p 127.0.0.1:8444:8444 \ kong:latest
使用grpc-web插件
完成了Kong的安装和初始化,简单的grpc服务也完成,现在就按照grpc-web官网上的方法来设置grpc-web插件。使用的是Kong admin命令来设置,最新版的kong-dashboard暂时没有升级到支持最新版的KONG,
第一步,创建grpc服务
curl -XPOST localhost:8001/services \--data name=grpc \--data protocol=grpc \--data host=localhost \--data port=6565
第二步,创建http route
curl -XPOST localhost:8001/services/grpc/routes \--data protocols=http \--data name=web-service \--data paths=/
第三步,为此route设置grpc-web
curl -XPOST localhost:8001/routes/web-service/plugins \ --data name=grpc-web
部署完成后,访问网址http://localhost:8000,返回400错误,原因unkonwn path /。
问题分析
出现这个错误,说明KONG已经接收到了此请求,但后端grpc服务没有收到请求,因此问题就出现在grpc-web插件上,于是打开grpc-web插件源码来进行分析。找到下面的代码片段:
local dec, err = deco.new( kong_request_get_header("Content-Type"), kong_request_get_path(), conf.proto)if not dec then kong.log.err(err) return kong_response_exit(400, err)end
此代码段的第五行返回400错误,因此问题可能发生在deco.new方法上,此方法返回nil导致错误发生,于是继续向上分析deco.new方法,代码如下:
function deco.new(mimetype, path, protofile)local text_encoding = text_encoding_from_mime[mimetype]local framing = framing_form_mime[mimetype]local msg_encoding = msg_encodign_from_mime[mimetype]local input_type, output_typeif msg_encoding ~= "proto" thenif not protofile thenreturn nil, "transcoding requests require a .proto file defining the service"end input_type, output_type = rpc_types(path, protofile)if not input_type thenreturn nil, output_typeendendreturn setmetatable({ mimetype = mimetype, text_encoding = text_encoding, framing = framing, msg_encoding = msg_encoding, input_type = input_type, output_type = output_type, }, deco)end
此处,有两处返回nil,第一处返回nil,应该不可能,因为我们设置了proto文件,所以怀疑应该是第二处的nil导致的,因此继续向上查找rpc_types方法,代码如下:
local function rpc_types(path, protofile)if not protofile thenreturn nilendlocal info = get_proto_info(protofile)local types = info[path]if not types thenreturn nil, ("Unkown path %q"):format(path)endreturn types[1], types[2]end
此代码中出错信息是Unknown path,因此基本断定是此处返回的错误信息,是由于info这个表中不能那个找到我们传入的path,继续向上分析如何产生这个info表的。也就是get_proto_info方法。
local function get_proto_info(fname)local info = _proto_info[fname]if info thenreturn infoendlocal p = protoc.new()local parsed = p:parsefile(fname) info = {}for _, srvc in ipairs(parsed.service) dofor _, mthd in ipairs(srvc.method) do info[("/%s.%s/%s"):format(parsed.package, srvc.name, mthd.name)] = { mthd.input_type, mthd.output_type, }endend _proto_info[fname] = info p:loadfile(fname)return infoend
从这个方法就能明白是如何产生info表的,首先从缓存中获取,如果能获取到直接返回,否则就解析proto文件生成info表,生成规则是proto里的package name,servicename和method name作为key,格式为info[("/%s.%s/%s"):format(parsed.package, srvc.name, mthd.name)] ,方法的input_type和output_type作为value。因此我么传入的/这个key肯定不能存在,所以会返回400错误。
水落石出
问题定位到了,解决方法就比较简单,在创建route时,path不能按照文档的实例中传入简单的/,而应该传入/package name.service name/method name这种格式。于是将创建route那步改成如下请求即可。
curl -XPOST localhost:8001/services/grpc/routes \--data protocols=http \--data name=web-service \--data paths=/com.example.grpc.helloworld.HelloWorldService/sayHello
测试
curl -H "Content-type: application/json" -XPOST -d '{"first_name": "david","last_name": "zhang"}' http://localhost:8000/com.example.grpc.helloworld.HelloWorldService/sayHello
返回字符串:hello,david zhang!
写在最后
虽然花了很多时间最后解决了此问题,也能收获一点点的成就感,但总觉得此插件官网的文档太过简单,不知道是不是代码和文档没有配套更新导致的,理论上不应该犯这种低级错误,而且同一篇文章被引用了很多地方,都是同一种配置方法,估计都没有亲自验证就发布了。联系到前几天看到一篇公众号文章,发现里面有些问题,所以给作者留言了,我倒不是要较真,只是觉得一篇受众那么广的博客,还是需要对读者负责,不能误导读者。作者倒反馈很迅速,只是不太友好,哪里错误?说我没有亲自验证,呵呵,当我把错误列出来后,也没再回复我,悄悄的把错误改了。
spring boot 配置网关时404错误_Kong中使用grpcweb插件代理grpc服务时遇到的坑相关推荐
- spring boot 配置网关时404错误_网关Spring Cloud Gateway科普
Spring Cloud Gateway是在Spring生态系统之上构建的API网关服务,它旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能, 例如:熔断.限流.重试等. ...
- spring boot配置logback日志
spring boot配置logback日志 在上一篇spring boot入门里提过,Spring Boot默认的日志打印是logback,所以配置logback日志会很简单,但是也有注意的地方. ...
- spring boot配置mybatis和事务管理
spring boot配置mybatis和事务管理 一.spring boot与mybatis的配置 1.首先,spring boot 配置mybatis需要的全部依赖如下: <!-- Spri ...
- 超全、超详的Spring Boot配置讲解笔记
超全.超详的Spring Boot配置讲解笔记 springboot默认加载配置 SpringBoot使用两种全局的配置文件,全局配置文件可以对一些默认配置进行修改. application.prop ...
- Spring Boot 2.0 配置图文教程第 2 章 Spring Boot 配置## 书信息 demo.book.name=[Spring Boot 2.x Core Action] demo.b
本章内容 1.自定义属性快速入门 2.外化配置 3.自动配置 4.自定义创建 Starter 组件 摘录:读书是读完这些文字还要好好用心去想想,写书也一样,做任何事也一样 第 2 章 Spring B ...
- spring boot配置详情
spring boot配置详情如下: 1.MVC相关 mvc spring.mvc.async.request-timeout设定async请求的超时时间,以毫秒为单位,如果没有设置的话,以具体 ...
- java 502错误,Spring Boot连接超时导致502错误的实战案例
1.问题描述 内部系统之间通过Nginx来实现路由转发. 但最近发现有一个系统,经常报502错误,每天达到上百次,完全无法忍受. 2. 原因排查 于是进行排查, 发现配置人员把连接超时时间(serve ...
- Spring Boot 配置元数据指南
点击蓝色"程序猿DD"关注我 回复"资源"获取独家整理的学习资料! 作者 | 遗失的拂晓 来源 | 公众号「锅外的大佬」 1. 概览 在编写 Spring Bo ...
- java多个数据库数据进行访问_通过Spring Boot配置动态数据源访问多个数据库的实现代码...
之前写过一篇博客<Spring+Mybatis+Mysql搭建分布式数据库访问框架>描述如何通过Spring+Mybatis配置动态数据源访问多个数据库.但是之前的方案有一些限制(原博客中 ...
最新文章
- leetcode-102 二叉树的层次遍历
- NOIP2001-普及组复赛-第一题-数的计算
- n个结点,不同形态的二叉树(数目+生成)
- xml分析错误:注释未终止_错误:C中的未终止注释(无效的注释块) 常见的C程序错误...
- 10个JavaScript代码片段,帮助你成为更好的开发者
- 跟我一起学Angular2(1)-了解ng模块化
- mysql-cluster 安装篇(1)---简介
- [转载] SimpleHTTPServer解释:如何使用Python发送文件
- SQL Sever — 附加【如何导入外部文件数据库】
- 位图和矢量图转换工具推荐
- 飞思卡尔16位单片机(四)——GPIO输入功能测试
- Multisim的学习记录(一)
- DEDECMS5.7自动采集更新伪原创插件高级版GBK
- samba将远程服务器映射到本地磁盘
- ubuntu18下载utuntu18镜像
- 餐饮app开发市场前景如何?行业竞争激烈吗?
- 在DAZ3D STUDIO中使用.OBJ和.FBX三维模型
- VS编译时一些常见错误积累LNK,比如LNK2019、LNK2001(实时更新)
- D2550运行Linux,也发一个128*128的相框lcd4linux的conf
- 《数据库原理》学生表,课程表,选课表的相关内容
热门文章
- TurboMail邮件服务器推动邮件领域的进一步发展
- OpenCV-PS扩散效果(毛玻璃)
- OpenCV-巴特沃斯低通高通滤波器(C++)
- linux系统服务器怎么登录日志文件,Linux服务器查看日志的几种方法
- java静态注解处理器_java – 使用mapstruct中的builder(使用immutables注释处理器)将对象映射到不可变对象...
- 学校计算机机房台账,机房工作
- oracle增量和全量的区别,ORACLE全备份和0级增量备份的区别
- 针对自动化测试的23种Node.js优秀实践
- linux中产生随机数函数,如何用C++产生随机数
- 监听返回app_基于 Redis 消息队列实现 Laravel 事件监听及底层源码探究