2021年6月5日土曜日

denoでhttp接続用portを自動選択する(修正版)

deno上で動作するデスクトップアプリを数種類作成して、必要に応じて起動させたり終了させたりしたい関係で、TCP接続に使えるポート番号を探すサンプルを先日作成しましたが、意図した通りの動作になっていませんでした。
具体的には、使用中のポート番号でのサーバ起動をトライしたタイミングでエラーが発生してしまうという状態でした。
上記の問題を解消したバージョンのサンプルが以下。



他のプロセスで使用されているポート番号でのサーバ起動をトライすると、以下のように表示されます。



「try」で囲んでいるため、エラーが発生した場合は「catch{ ~ }」が実行されると思っていましたが、そのようには動作しませんでした。
import { serve } from "https://deno.land/std@0.86.0/http/server.ts";
async function サーバ起動後(サーバ: any){
for await (const request of サーバ) {
let bodyContent = "Your user-agent is:\n\n";
bodyContent += request.headers.get("user-agent") || "Unknown";
request.respond({ status: 200, body: bodyContent });
}
}
for(let port=49152; port<=65535 ;port++){
try{
const server = serve({ hostname: "0.0.0.0", port: port });
console.log(`HTTP webserver running. Access it at: http://localhost:` + port + `/`);
サーバ起動後(server)
break
}
catch(e){}
}



仕方がないので、以下のようにしました。
  1. netstatコマンドを使って使用中のポート番号のリストを得る。
  2. findコマンドを使って上記のリストから、任意のポート番号を含む行を抽出する。
  3. 上記を行うバッチファイルを作成する。
  4. Deno.runで上記バッチファイルを実行して、標準出力を解釈してポート番号が使用中か否かを判定する。
上記の3については、findコマンドのパラメータはダブルクォーテーションで囲む必要があるがdeno.Runではその記号を渡す際に\マークが入ってしまうので、やむを得ずそのような対応となりました。
上記赤字の問題については後日、別の投稿を作成します。



修正版のソースが以下。
import { serve } from "https://deno.land/std@0.86.0/http/server.ts";
async function 空いてるport番号を返す(): Promise<number>{
let pathBat = await 一時ファイルを作成('netstat -na | find "%~1"')
if(pathBat.match(/[  ]/)){
throw new Error('一時ファイルのPathにスペースが含まれています。\n'+pathBat+'\nDeno.runでは「"」の記号の前に「\\」が挿入されてしまうため、使用できません。')
}
for(let port=49152; port<=65535 ;port++){
const p = Deno.run({cmd:['cmd', '/C', 'chcp 65001 & '+pathBat+' ' + port], stdout:'piped'})
const o = await p.output()
const text = new TextDecoder().decode(o)
console.log('text:\n'+text)
if(text.indexOf(':'+port)==-1){
Deno.remove(pathBat)
return port
}
}
throw new Error('throw from port num search')
}
async function 一時ファイルを作成(value:string){
const path = await Deno.makeTempFile({prefix: 'deno_tmp', suffix:'.bat'})
await Deno.writeTextFile(path, value)
return path
}
async function ブラウザからのリクエストに対応開始する(サーバ: any){
for await (const request of サーバ) {
let bodyContent = "Your user-agent is:\n\n";
bodyContent += request.headers.get("user-agent") || "Unknown";
request.respond({ status: 200, body: bodyContent });
}
}
function ブラウザ起動(port: number){
Deno.run({cmd:['cmd', '/C start http://localhost:'+port]})
}
const port = await 空いてるport番号を返す()
const server = serve({ hostname: "0.0.0.0", port: port })
console.log(`HTTP webserver running. Access it at: http://localhost:` + port + `/`)
ブラウザからのリクエストに対応開始する(server)
ブラウザ起動(port)



修正版の実行結果。


49152~49153が使用中の状況で上記のサンプルを実行すると、以下のようになりますので、意図した通りの動作になっている事が確認できました。



うまくいくケースだけじゃなく、失敗する筈のケースも動作確認するべき…とは思っているのですが、忘れがちです…。反省。

0 件のコメント:

コメントを投稿