2025年9月17日水曜日

gRPCやってみる(7)

 

gRPC-Webってのをやってみる。HTMLに埋め込んだJavaScriptから呼び出すにはもう少し手を加える必要があった。


HTMLに埋め込んだJavaScriptから呼び出すにはサーバー側がgRPC-Webじゃないといけないんだけど、前回のままでは同一オリジンポリシーではじかれるってのと、サーバー側がHTTP/2で待っているけど、ブラウザはHTTP/1.1で話しかけるから通じないってのがあってそこに手を入れないといけない。
んで、サーバーの作り方は前回と全く一緒なんだけど、Program.csだけちょこっと追加がある。
同一オリジンポリシーの対策はCORSって言うらしくてMicrosoftの公式サイトを参考に。サーバーがHTTP/1.1をサポートするようにする方法はGeminiに聞いた。
んで、変更点はソースコード内のコメントを見てちょ

Program.cs
  1. using grpcweb_srv.Services;
  2.  
  3. var builder = WebApplication.CreateBuilder(args);
  4.  
  5. // gRPC-Webクライアントとの通信のために、KestrelをHTTP/1.1で動作させるよう設定します。
  6. builder.WebHost.ConfigureKestrel(options =>
  7. {
  8.     options.ListenLocalhost(5052, o => o.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http1);
  9. });
  10.  
  11. // gRPCサービスとCORSポリシーをアプリケーションに登録します。
  12. builder.Services.AddGrpc();
  13. builder.Services.AddCors(o => o.AddPolicy("AllowAll", builder =>
  14. {
  15.     builder.AllowAnyOrigin()
  16.             .AllowAnyMethod()
  17.             .AllowAnyHeader()
  18.             .WithExposedHeaders("Grpc-Status", "Grpc-Message",
  19.                 "Grpc-Encoding", "Grpc-Accept-Encoding",
  20.                 "Grpc-Status-Details-Bin");
  21. }));
  22.  
  23. var app = builder.Build();
  24.  
  25. // gRPC-Webミドルウェアを有効にします。
  26. app.UseGrpcWeb();
  27.  
  28. // CORSミドルウェアを有効にします。
  29. app.UseCors();
  30.  
  31. // Configure the HTTP request pipeline.
  32. app.MapGrpcService<GreeterService>().EnableGrpcWeb().RequireCors("AllowAll");
  33. app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
  34.  
  35. app.Run();

これで、dotnet buildからのdotnet runしておく。

2025年9月15日月曜日

gRPCやってみる(6)

 

gRPC-Webってのをやってみる。

気を取り直して、Microsoftの公式ページを参考にやってみる。

サーバー

普通に
dotnet new grpc
Grpc.AspNetCore.Web パッケージへの参照を追加、、、するらしい
dotnet add package Grpc.AspNetCore.Web
Program.csを編集("<--"って部分を見てちょ)
  1. using grpcweb_srv.Services;
  2.  
  3. var builder = WebApplication.CreateBuilder(args);
  4.  
  5. // Add services to the container.
  6. builder.Services.AddGrpc();
  7.  
  8. var app = builder.Build();
  9.  
  10. app.UseGrpcWeb();//<-- added
  11. // Configure the HTTP request pipeline.
  12. //app.MapGrpcService<GreeterService>(); //<-- commented out
  13. app.MapGrpcService<GreeterService>().EnableGrpcWeb(); //<-- added
  14. app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
  15.  
  16. app.Run();
これでサーバーはokらしいので、
dotnet build
dotnet run
してみる
まぁこんなかんじ

クライアント

普通に
dotnet new console
次のパッケージのプロジェクト ファイルにパッケージ参照を追加
Grpc.Net.Client.Web
Grpc.Net.ClientFactory
、、、らしい
dotnet add package Grpc.Net.Client.Web
dotnet add package Grpc.Net.ClientFactory
Protoを使う以上は
dotnet add package Google.Protobuf
は必要なんよね;
これも
dotnet add package Grpc.Tools
Protoファイルをサーバーのプロジェクトフォルダからcopy
mkdir Protos; cp ../grpcweb-srv/Protos/greet.proto Protos
Protos/greet.protoを編集
  1. syntax = "proto3";
  2.  
  3. option csharp_namespace = "grpcweb_cli";//<--changed _srv to _cli
  4.  
  5. package greet;
  6.  
  7. // The greeting service definition.
  8. service Greeter {
  9.   // Sends a greeting
  10.   rpc SayHello (HelloRequest) returns (HelloReply);
  11. }
  12.  
  13. // The request message containing the user's name.
  14. message HelloRequest {
  15.   string name = 1;
  16. }
  17.  
  18. // The response message containing the greetings.
  19. message HelloReply {
  20.   string message = 1;
  21. }
grpcweb-cli.csprojを編集、、、
  •   <ItemGroup>
  •     <Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
  •   </ItemGroup>
を追加する
  1. <Project Sdk="Microsoft.NET.Sdk">
  2.  
  3.   <PropertyGroup>
  4.     <OutputType>Exe</OutputType>
  5.     <TargetFramework>net8.0</TargetFramework>
  6.     <RootNamespace>grpcweb_cli</RootNamespace>
  7.     <ImplicitUsings>enable</ImplicitUsings>
  8.     <Nullable>enable</Nullable>
  9.   </PropertyGroup>
  10.  
  11.   <ItemGroup>
  12.     <PackageReference Include="Google.Protobuf" Version="3.32.1" />
  13.     <PackageReference Include="Grpc.Net.Client.Web" Version="2.71.0" />
  14.     <PackageReference Include="Grpc.Net.ClientFactory" Version="2.71.0" />
  15.     <PackageReference Include="Grpc.Tools" Version="2.72.0">
  16.       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  17.       <PrivateAssets>all</PrivateAssets>
  18.     </PackageReference>
  19.   </ItemGroup>
  20.  
  21.   <ItemGroup>
  22.     <Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
  23.   </ItemGroup>
  24.  
  25. </Project>
Program.csを編集
  1. using Google.Protobuf;
  2. using Grpc.Net.Client;
  3. using Grpc.Net.Client.Web;
  4. using grpcweb_cli;
  5.  
  6. var channel = GrpcChannel.ForAddress("http://localhost:5056", new GrpcChannelOptions
  7. {
  8.     HttpHandler = new GrpcWebHandler(new HttpClientHandler())
  9. });
  10.  
  11. var client = new Greeter.GreeterClient(channel);
  12. var response = await client.SayHelloAsync(
  13.                   new HelloRequest { Name = "GreeterClient" });
  14. Console.WriteLine("Greeting: " + response.Message);
  15. Console.WriteLine("Press any key to exit...");
  16. Console.ReadKey();
(ポート番号はサーバーを起動して確認する<--固定化する方法をいつか調べないとな)
以前作ったのと比較すると、
using Grpc.Net.Client.Web;
を追加
channelインスタンス生成がなんか複雑になった
、、、そのほかは同じかな
で、dotnet buildからのdotnet run
(サーバーは起動しておこう)
となって、うまくいったがね。
で、HTMLに埋め込んだJavaScriptでクライアントをやってみるってのに進んでいくわけだけど、うまくいくかどうかはまだ不明;
Geminiに奪われた時間を無理やり取り戻してやったぜ

gRPCやってみる(5) -内容なし-

 

gRPC-Webってのをやってみる。

Geminiが言うには今までやってきたのは生のgRPC(HTTP/2)らしい。で、ブラウザで利用するとかだとgRPC-Webってやつじゃないといかんらしい。
で、gRPC-WebってのをGeminiに相談しながら試そうとしてたんだけど、ビルドすると「AddGrpcWeb」メソッドはないってエラーが返されて、Geminiにあーでもないこーでもない言われながらやっても全然だめで、google検索結果では「AddGrpcWeb」ってのはないって言われたよ?って質問したら、
なんか信者みたいな回答が来て、はぁ?って思いながら「AddGrpcWeb」が必要であると記述されている公式な資料を示すよう要求したら、Microsoftのページを紹介してきたのでそれ読んでみたら「AddGrpcWeb」なんて出てこないわけよ。んで、Geminiにここ読んでみろやって言ったら、
...


時間ドロボーの被害にあいました߹ㅁ߹)

2025年9月12日金曜日

JavaScript今さら入門(2)

 

今さらなんだけど、JavaScriptに挑戦してみる。唐突にJavaScriptでgRPC Clientを作ってみる。


今さらなんだけど、JavaScriptに挑戦してみるシリーズ。、、、なんだけど、突然ハードルを上げてgRPC Clientを作ってみたいと思ったわけよ。まだJavaScript初心者なんだけど、、、
んで、「javascript grpc client」とかで検索してあちこち見てみると、やっぱりまずはNode.jsが必要って書いてあるので、しぶしぶ従う。んで、Node.jsの総本山をみるとDockerでインストールとかあるんだけど、意味わからんので普通にインストールできんもんか探ってみた。で、
Linux 環境に Node.js インストール
が当方のニーズにマッチしてそうなので、そのままやってみる。んだけど、「実行してみる」の部分が不親切でなにやっていいかわからんので、この部分はやらない。
そもそもNode.jsってなんなんや?って思うんやけど、、、で、それをうまいこと説明しているサイトを見つけた。
Node.jsとはなにか?なぜみんな使っているのか?
こちらのサイトとってもわかりやすい。すばらしい。

では、gRPCやってみよーって何していいかわからんぞ、、、こういう時はAIに聞くのじゃ
プロジェクトのセットアップ
まず、新しいプロジェクトフォルダを作成し、初期化します。
  • mkdir grpc-client-demo
  • cd grpc-client-demo
  • npm init -y
そすっと、

必要なパッケージのインストール
gRPCクライアントを構築するために、いくつかのパッケージをインストールします。
@grpc/grpc-js: gRPCのコアパッケージ
google-protobuf: Protocol Buffersを扱うためのパッケージ
grpc-tools: .protoファイルからJavaScriptのコードを生成するためのツール
@grpc/proto-loader: 動的に.protoファイルを読み込むためのパッケージ (後述)
  • npm install @grpc/grpc-js google-protobuf
  • npm install --save-dev grpc-tools
そすっと

Protocol Buffers (.proto) ファイルの準備
gRPCでは、サービスとメッセージの定義を.protoファイルで行います。サーバー側とクライアント側で同じ.protoファイルを使用します。ここでは例として、grpc-client-demo.protoというファイルを作成します。
protoディレクトリを作成し、grpc-client-demo.protoを作成します。
  • mkdir proto
grpc-client-demo.protoはgRPCやってみる(4)から持ってきて、
  • syntax = "proto3";
  • //option csharp_namespace = "rijndael_cli";
  • package rijndael_srv;
  • service AES128EncSrv {
  •   rpc AES128Enc (AES128EncRequest) returns (AES128EncReply);
  • }
  • message AES128EncReply{
  •   bytes encoded=1;
  • }
  •  
  • message AES128EncRequest{
  •   bytes plain=1;
  •   bytes key=2;
  • }

.protoファイルからJavaScriptコードを生成
.protoファイルからgRPCクライアントが利用するJavaScriptコードを生成します。package.jsonのscriptsにコマンドを追加すると便利です。

package.json
  1. {
  2.   "name": "grpc-client-demo",
  3.   "version": "1.0.0",
  4.   "description": "",
  5.   "main": "index.js",
  6.   "scripts": {
  7.     "proto:gen": "grpc_tools_node_protoc --js_out=import_style=commonjs,binary:./gen --grpc_out=grpc_js:./gen --plugin=protoc-gen-grpc=./node_modules/.bin/grpc_tools_node_protoc_plugin --proto_path=./proto ./proto/*.proto",
  8.     "test": "echo \"Error: no test specified\" && exit 1"
  9.   },
  10.   "keywords": [],
  11.   "author": "",
  12.   "license": "ISC",
  13.   "dependencies": {
  14.     "@grpc/grpc-js": "^1.13.4",
  15.     "google-protobuf": "^4.0.0"
  16.   },
  17.   "devDependencies": {
  18.     "grpc-tools": "^1.13.0"
  19.   }
  20. }

(07行目を追加しただけ。)
上記のコマンドを実行すると、genディレクトリが作成され、その中に以下のファイルが生成されます。
helloworld_pb.js: HelloRequestやHelloReplyといったメッセージを扱うためのコード
helloworld_grpc_pb.js: gRPCサービス (Greeterなど) を扱うためのコード

生成コマンドを実行します。
  • npm run proto:gen

ってすると、genディレクトリがないってエラーが出る。Geminiは自動でできるって言ってるけど自動でできないっぽい。ので事前に作っておく。
mkdir gen
んで、
npm run proto:gen
こうなる。


gRPCクライアントのコード作成
index.jsという名前でクライアントのコードを作成します。
てか、ここからはさすがに大手術。、、、なんだけど、齢50のおっさんにはつらい;ので、gRPCやってみる(4)のclientのC#コードをJavaScriptに変換するようにGeminiにお願いする。「以下のC#コードをJavaScriptに変換してください」って。んで、最初に出てきたのがこれまでの流れを知らない風のコードで、Geminiが言うには、gPRC-WebとかいうgRPCのスタイルで出力したらしい。「grpc.loadPackageDefinitionを使用するスタイルで」っていうと、いい感じで出力してくれた。こちらは生のgRPC(HTTP/2)を扱うNode.js環境で主に使用される方法なんだと。
index.js
  1. const PROTO_PATH = './proto/grpc-client-demo.proto';
  2. const grpc = require('@grpc/grpc-js');
  3. const protoLoader = require('@grpc/proto-loader');
  4. // gRPCサービスの定義を動的に読み込む
  5. const packageDefinition = protoLoader.loadSync(
  6.     PROTO_PATH,
  7.     {keepCase: true,
  8.      longs: String,
  9.      enums: String,
  10.      defaults: true,
  11.      oneofs: true
  12.     });
  13. // パッケージ定義からgRPCサービスをロード
  14. const rijndael_srv = grpc.loadPackageDefinition(packageDefinition).rijndael_srv;
  15. async function main() {
  16.     const b_plain = Buffer.from([0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]);
  17.     const b_key = Buffer.from([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f]);
  18.     // クライアントインスタンスを作成
  19.     // 引数にはサーバーアドレス、認証情報、オプションを指定
  20.     const client = new rijndael_srv.AES128EncSrv('localhost:5115', grpc.credentials.createInsecure());
  21.     // RPC呼び出し
  22.     client.AES128Enc({
  23.         plain: b_plain,
  24.         key: b_key
  25.     }, (error, reply) => {
  26.         if (error) {
  27.             console.error('Error:', error.details);
  28.             return;
  29.         }
  30.         const b_encrypted = reply.encoded;
  31.         // 16進数文字列に変換して出力
  32.         const txt = b_encrypted.toString('hex').toUpperCase();
  33.         console.log(txt);
  34.     });
  35. }
  36. main();
んで、実行。
node index.js
すばらしい!ちゃんとできてる。
ちなみにサーバーはgRPCやってみる(4)の手順で再度作り直した(dotnetのバージョンを変えてしまってて、dotnet buildできんかったので)。
いきなり難しい挑戦だったけど、Geminiのおかげでやりきれたー。JavaScriptのコード書くところなんて、自力じゃ絶対にムリ。間違いなく途方に暮れてしばらくお蔵に入れるくらいさっぱりわからんかった。こんな難しいのをやすやすと乗り越えられる時代になったんだなー。


ところで、DX人材認定制度とかある会社が増えてると思うんやけど、Power AutomateとかPower Appsとかだけなの悲しい。

2025年9月3日水曜日

JavaScript今さら入門(1)

 

今さらなんだけど、JavaScriptに挑戦してみる。


ほんと今さらなんだけど、今まで完全にスルーしていたJavaScriptに挑戦してみる。GUIのあるアプリを書く際に、pythonだとtkinterのコードをpyに埋め込むけど、まぁそれならGUIをHTMLで記述して処理をJavaScriptで書いてもいいじゃんねって思った(マジで今さら)。

まずは、開発環境ってことで、VSCodeだけあればいいらしい。開発用のローカルサーバーとか必要そうだけど、そういうのは今は書かない。
プロジェクトってかコードを置くフォルダを開いた状態で
新規ファイルを作成。名前はtest0.htmlってする。
エディタ部に
html
っていれると、こんなになるので、
html:5
ってのを選ぶ。すると、
って感じで、ひな型を作ってくれる。
で、これを、こんな感じにする。
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>Document</title>
  7. </head>
  8. <body>
  9.     <div id="hello"></div><br>
  10.     <script>
  11.         let hello=document.getElementById('hello');
  12.         hello.insertAdjacentHTML('afterend','<H1>Hello World!!</H1>')
  13.     </script>
  14. </body>
  15. </html>
scriptタグで囲われた部分がJavaScriptで、htmlからIDが"hello"ってやつを探してきて、そこにHTMLを流し込むってことかな
で、htmlをchromeで開くとこうなる。
たぶん最もめんどくさいHello World。
んで、もちっと込み入ったこともしてみる。入力ボックスから値をとってボタンでアクションする。
いきなりだけど、こんな感じ(ちなみに、見やすいようにhtmlとJavaScriptコードを分割した)

test1.html
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>Document</title>
  7. </head>
  8. <body>
  9.     <div id="out0"></div><br>
  10.     <input type="number" id="in1" placeholder="ここに数字を入力"><br>
  11.     <input type="number" id="in2" placeholder="ここに数字を入力"><br>
  12.     <button type="button" id="button1">足す</button><br>
  13.     <div id="out1"></div><br>
  14.     <script src="test1-src.js"></script>
  15. </body>
  16. </html>

test1-src.js
  1. let out0=document.getElementById('out0');
  2. let out1=document.getElementById('out1');
  3. let in1=document.getElementById('in1');
  4. let in2=document.getElementById('in2');
  5. let button1=document.getElementById('button1');
  6. out0.insertAdjacentHTML('afterend','Hello World!!')
  7. function add(a, b) {
  8.     return a + b;
  9. }
  10. function button1_clicked(){
  11.     let in1_value = parseInt(in1.value,10);
  12.     let in2_value = parseInt(in2.value,10);
  13.     let result = add(in1_value, in2_value);
  14.     out1.insertAdjacentHTML('afterend',result);
  15. }
  16. button1.addEventListener("click",button1_clicked);

htmlを開くとこうなる
数字を入れてボタンを押すとこうなる
うまくいった(*´꒳`*)