在大型分布式系统中,有很多的微服务对外提供服务,也会有各种微服务的协议需要集成,比如http,https,grpc的,这时就需要一个API网关提供高性能、高可用的API托管服务,帮助服务的开发者便捷地对外提供服务,而不用考虑安全控制、流量控制、审计日志等问题,统一在网关层将安全认证,流量控制,审计日志,黑白名单等实现。网关的下一层,是内部服务,内部服务只需开发和关注具体业务相关的实现。网关可以提供API发布、管理、维护等主要功能。开发者只需要简单的配置操作即可把自己开发的服务发布出去,同时置于网关的保护之下。
我们项目中使用的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-webprotobuf-maven-pluginbuild-helper-maven-pluginspring-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服务时遇到的坑相关推荐

  1. spring boot 配置网关时404错误_网关Spring Cloud Gateway科普

    Spring Cloud Gateway是在Spring生态系统之上构建的API网关服务,它旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能, 例如:熔断.限流.重试等. ...

  2. spring boot配置logback日志

    spring boot配置logback日志 在上一篇spring boot入门里提过,Spring Boot默认的日志打印是logback,所以配置logback日志会很简单,但是也有注意的地方. ...

  3. spring boot配置mybatis和事务管理

    spring boot配置mybatis和事务管理 一.spring boot与mybatis的配置 1.首先,spring boot 配置mybatis需要的全部依赖如下: <!-- Spri ...

  4. 超全、超详的Spring Boot配置讲解笔记

    超全.超详的Spring Boot配置讲解笔记 springboot默认加载配置 SpringBoot使用两种全局的配置文件,全局配置文件可以对一些默认配置进行修改. application.prop ...

  5. 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 ...

  6. spring boot配置详情

    spring boot配置详情如下:  1.MVC相关  mvc  spring.mvc.async.request-timeout设定async请求的超时时间,以毫秒为单位,如果没有设置的话,以具体 ...

  7. java 502错误,Spring Boot连接超时导致502错误的实战案例

    1.问题描述 内部系统之间通过Nginx来实现路由转发. 但最近发现有一个系统,经常报502错误,每天达到上百次,完全无法忍受. 2. 原因排查 于是进行排查, 发现配置人员把连接超时时间(serve ...

  8. Spring Boot 配置元数据指南

    点击蓝色"程序猿DD"关注我 回复"资源"获取独家整理的学习资料! 作者 | 遗失的拂晓 来源 | 公众号「锅外的大佬」 1. 概览 在编写 Spring Bo ...

  9. java多个数据库数据进行访问_通过Spring Boot配置动态数据源访问多个数据库的实现代码...

    之前写过一篇博客<Spring+Mybatis+Mysql搭建分布式数据库访问框架>描述如何通过Spring+Mybatis配置动态数据源访问多个数据库.但是之前的方案有一些限制(原博客中 ...

最新文章

  1. leetcode-102 二叉树的层次遍历
  2. NOIP2001-普及组复赛-第一题-数的计算
  3. n个结点,不同形态的二叉树(数目+生成)
  4. xml分析错误:注释未终止_错误:C中的未终止注释(无效的注释块) 常见的C程序错误...
  5. 10个JavaScript代码片段,帮助你成为更好的开发者
  6. 跟我一起学Angular2(1)-了解ng模块化
  7. mysql-cluster 安装篇(1)---简介
  8. [转载] SimpleHTTPServer解释:如何使用Python发送文件
  9. SQL Sever — 附加【如何导入外部文件数据库】
  10. 位图和矢量图转换工具推荐
  11. 飞思卡尔16位单片机(四)——GPIO输入功能测试
  12. Multisim的学习记录(一)
  13. DEDECMS5.7自动采集更新伪原创插件高级版GBK
  14. samba将远程服务器映射到本地磁盘
  15. ubuntu18下载utuntu18镜像
  16. 餐饮app开发市场前景如何?行业竞争激烈吗?
  17. 在DAZ3D STUDIO中使用.OBJ和.FBX三维模型
  18. VS编译时一些常见错误积累LNK,比如LNK2019、LNK2001(实时更新)
  19. D2550运行Linux,也发一个128*128的相框lcd4linux的conf
  20. 《数据库原理》学生表,课程表,选课表的相关内容

热门文章

  1. TurboMail邮件服务器推动邮件领域的进一步发展
  2. OpenCV-PS扩散效果(毛玻璃)
  3. OpenCV-巴特沃斯低通高通滤波器(C++)
  4. linux系统服务器怎么登录日志文件,Linux服务器查看日志的几种方法
  5. java静态注解处理器_java – 使用mapstruct中的builder(使用immutables注释处理器)将对象映射到不可变对象...
  6. 学校计算机机房台账,机房工作
  7. oracle增量和全量的区别,ORACLE全备份和0级增量备份的区别
  8. 针对自动化测试的23种Node.js优秀实践
  9. linux中产生随机数函数,如何用C++产生随机数
  10. 监听返回app_基于 Redis 消息队列实现 Laravel 事件监听及底层源码探究