Web 浏览器访问 Dubbo 服务

使用 dubbo-js 开发运行在浏览器页面的微服务。

基于 Dubbo3 定义的 Triple 协议,你可以轻松编写浏览器、gRPC 兼容的 RPC 服务,并让这些服务同时运行在 HTTP/1 和 HTTP/2 上。Dubbo TypeScript SDK 支持使用 IDL 或编程语言特有的方式定义服务,并提供一套轻量的 API 来发布或调用这些服务。

Dubbo-js 已于 9 月份发布支持 Dubbo3 协议的首个 alpha 版本,它的发布将有机会彻底改变微服务前后端的架构与通信模式,让你能直接在浏览器页面或web服务器中访问后端 Dubbo RPC 服务。

dubbo-web.png

浏览器 Web 应用示例

本示例演示了如何使用 dubbo-js 开发运行在浏览器上的 web 应用程序,web 页面将调用 dubbo node.js 开发的后端服务并生成页面内容。本示例演示基于 IDL 和非 IDL 两种编码模式。

dubbo-web.png

IDL 模式

前置条件

首先,我们将使用 Vite 来生成我们的前端项目模板,它内置了我们稍后需要的所有功能支持。

  1. npm create vite@latest -- dubbo-web-example --template react-ts
  2. cd dubbo-web-example
  3. npm install

因为使用 Protocol Buffer 的原因,我们首先需要安装相关的代码生成工具,这包括 @bufbuild/protoc-gen-es@bufbuild/protobuf@apachedubbo/protoc-gen-apache-dubbo-es@apachedubbo/dubbo

  1. npm install @bufbuild/protoc-gen-es @bufbuild/protobuf @apachedubbo/protoc-gen-apache-dubbo-es @apachedubbo/dubbo

使用 Proto 定义服务

现在,使用 Protocol Buffer (IDL) 来定义一个 Dubbo 服务。

src 下创建 util/proto 目录,并生成文件

  1. mkdir -p src/util/proto && touch src/util/proto/example.proto

写入内容

  1. syntax = "proto3";
  2. package apache.dubbo.demo.example.v1;
  3. message SayRequest {
  4. string sentence = 1;
  5. }
  6. message SayResponse {
  7. string sentence = 1;
  8. }
  9. service ExampleService {
  10. rpc Say(SayRequest) returns (SayResponse) {}
  11. }

这个文件声明了一个叫做 ExampleService 的服务,为这个服务定义了 Say 方法以及它的请求参数 SayRequest 和返回值 SayResponse

生成代码

创建 gen 目录,作为生成文件放置的目标目录

  1. mkdir -p src/util/gen

运行以下命令,利用 protoc-gen-esprotoc-gen-apache-dubbo-es 等插件在 gen 目录下生成代码文件

  1. PATH=$PATH:$(pwd)/node_modules/.bin \
  2. protoc -I src/util/proto \
  3. --es_out src/util/gen \
  4. --es_opt target=ts \
  5. --apache-dubbo-es_out src/util/gen \
  6. --apache-dubbo-es_opt target=ts \
  7. example.proto

运行命令后,应该可以在目标目录中看到以下生成的文件:

  1. ├── src
  2. ├── util
  3. ├── gen
  4. ├── example_dubbo.ts
  5. └── example_pb.ts
  6. └── proto
  7. └── example.proto

创建 App

需要先下载 @apachedubbo/dubbo-web

  1. npm install @apachedubbo/dubbo-web

现在我们可以从包中导入服务并设置一个客户端。在 App.tsx 中添加以下内容:

  1. import { useState } from "react";
  2. import "./App.css";
  3. import { createPromiseClient } from "@apachedubbo/dubbo";
  4. import { createDubboTransport } from "@apachedubbo/dubbo-web";
  5. // Import service definition that you want to connect to.
  6. import { ExampleService } from "./util/gen/example_dubbo";
  7. // The transport defines what type of endpoint we're hitting.
  8. // In our example we'll be communicating with a Dubbo endpoint.
  9. const transport = createDubboTransport({
  10. baseUrl: "http://localhost:8080",
  11. });
  12. // Here we make the client itself, combining the service
  13. // definition with the transport.
  14. const client = createPromiseClient(ExampleService, transport, { serviceGroup: 'dubbo', serviceVersion: '1.0.0' });
  15. function App() {
  16. const [inputValue, setInputValue] = useState("");
  17. const [messages, setMessages] = useState<
  18. {
  19. fromMe: boolean;
  20. message: string;
  21. }[]
  22. >([]);
  23. return (
  24. <>
  25. <ol>
  26. {messages.map((msg, index) => (
  27. <li key={index}>{`${msg.fromMe ? "ME:" : "Dubbo Server:"} ${msg.message}`}</li>
  28. ))}
  29. </ol>
  30. <form
  31. onSubmit={async (e) => {
  32. e.preventDefault();
  33. // Clear inputValue since the user has submitted.
  34. setInputValue("");
  35. // Store the inputValue in the chain of messages and
  36. // mark this message as coming from "me"
  37. setMessages((prev) => [
  38. ...prev,
  39. {
  40. fromMe: true,
  41. message: inputValue,
  42. },
  43. ]);
  44. const response = await client.say({
  45. sentence: inputValue,
  46. });
  47. setMessages((prev) => [
  48. ...prev,
  49. {
  50. fromMe: false,
  51. message: response.sentence,
  52. },
  53. ]);
  54. }}
  55. >
  56. <input value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
  57. <button type="submit">Send</button>
  58. </form>
  59. </>
  60. );
  61. }
  62. export default App;

执行以下命令,即可得到样例页面

  1. npm run dev

启动 Server

接下来我们需要启动 Server,可以使用 Java、Go、Node.js 等 Dubbo 支持的任一语言开发 Server。这里我们采用 Dubbo 服务嵌入的 Node.js 服务器,具体可参考 Node.js 开发 Dubbo 后端服务 中的操作步骤。

不过需要注意,我们额外需要修改 Node.js 示例:引入 @fastify/cors 来解决前端请求的跨域问题

  1. npm install @fastify/cors

需要在 server.ts 文件下修改

  1. ...
  2. import cors from "@fastify/cors";
  3. ...
  4. async function main() {
  5. const server = fastify();
  6. ...
  7. await server.register(cors, {
  8. origin: true,
  9. });
  10. ...
  11. await server.listen({ host: "localhost", port: 8080 });
  12. ...
  13. }
  14. void main();

最后,运行代码启动服务

  1. npx tsx server.ts

无 IDL 模式

在接下来的版本中,我们将继续提供无 IDL 模式的通信支持,这样就可以更方便的访问无 IDL 的后端服务。在这里,我们先快速的看一下无 IDL 模式的使用方式。

同样需要先安装 @apachedubbo/dubbo@apachedubbo/dubbo-web

  1. npm install @apachedubbo/dubbo @apachedubbo/dubbo-web

现在就可以一个启动一个客户端,并发起调用了。App.tsx 中的代码与 IDL 模式基本一致,区别点在于以下内容:

  1. // ...
  2. // set backend server to connect
  3. const transport = createDubboTransport({
  4. baseUrl: "http://localhost:8080",
  5. });
  6. // init client
  7. const client = createPromiseClient(transport);
  8. function App() {
  9. // ...
  10. // call remote Dubbo service
  11. const response = await client.call(
  12. "apache.dubbo.demo.example.v1.ExampleService",
  13. "say",
  14. {
  15. sentence: inputValue,
  16. });
  17. }

执行以下命令,即可得到样例页面

  1. npm run dev

最后修改 September 13, 2024: Refactor website structure (#2860) (1a4b998f54b)