検索
リンク
タグ
ASP.NET
.NET
ASP.NET MVC
F#
Visual Studio
Azure
ASP.NET Core
ライトニングトーク
Plone
AJAX
Selenium
C#
jQuery
ADO.NET Entity Framework
SQL Server
JavaScript
LINQ
WebMatrix
EFCore
Fizz-Buzz
カテゴリ
最新の記事
最新のコメント
記事ランキング
最新のトラックバック
以前の記事
2022年 12月 2022年 11月 2022年 10月 2022年 09月 2022年 08月 2022年 07月 2022年 06月 2022年 05月 2022年 04月 2022年 03月 2022年 02月 2022年 01月 2021年 12月 2021年 11月 2021年 10月 2021年 09月 2021年 08月 2021年 07月 2021年 06月 2021年 05月 2021年 04月 2021年 03月 2021年 02月 2021年 01月 2020年 12月 2020年 11月 2020年 10月 2020年 09月 2020年 08月 2020年 07月 2020年 06月 2020年 05月 2020年 04月 2020年 03月 2020年 02月 2020年 01月 2019年 12月 2019年 11月 2019年 10月 2019年 09月 2019年 08月 2019年 07月 2019年 06月 2019年 05月 2019年 04月 2019年 03月 2019年 02月 2019年 01月 2018年 12月 2018年 11月 2018年 10月 2018年 09月 2018年 08月 2018年 07月 2018年 06月 2018年 05月 2018年 04月 2018年 03月 2018年 02月 2018年 01月 2017年 12月 2017年 11月 2017年 10月 2017年 09月 2017年 08月 2017年 07月 2017年 06月 2017年 05月 2017年 04月 2017年 02月 2017年 01月 2016年 12月 2016年 11月 2016年 10月 2016年 09月 2016年 08月 2016年 07月 2016年 06月 2016年 05月 2016年 04月 2016年 03月 2016年 02月 2016年 01月 2015年 12月 2015年 11月 2015年 10月 2015年 09月 2015年 08月 2015年 07月 2015年 05月 2015年 04月 2015年 03月 2015年 02月 2015年 01月 2014年 12月 2014年 11月 2014年 10月 2014年 09月 2014年 08月 2014年 06月 2014年 04月 2014年 03月 2014年 02月 2014年 01月 2013年 12月 2013年 10月 2013年 09月 2013年 08月 2013年 07月 2013年 06月 2013年 05月 2013年 04月 2013年 03月 2013年 02月 2013年 01月 2012年 12月 2012年 11月 2012年 10月 2012年 09月 2012年 08月 2012年 07月 2012年 06月 2012年 05月 2012年 04月 2012年 03月 2012年 02月 2012年 01月 2011年 12月 2011年 11月 2011年 10月 2011年 09月 2011年 08月 2011年 07月 2011年 06月 2011年 05月 2011年 04月 2011年 03月 2011年 02月 2011年 01月 2010年 12月 2010年 11月 2010年 10月 2010年 09月 2010年 08月 2010年 07月 2010年 06月 2010年 05月 2010年 04月 2010年 03月 2010年 02月 2010年 01月 2009年 12月 2009年 10月 2009年 09月 2009年 07月 2009年 06月 2009年 05月 2009年 04月 2009年 03月 2009年 02月 2009年 01月 2008年 12月 2008年 11月 2008年 10月 2008年 09月 2008年 08月 2008年 07月 2008年 06月 2008年 05月 2008年 04月 2008年 03月 2008年 02月 2008年 01月 2007年 12月 2007年 11月 2007年 04月 2007年 03月 2007年 02月 2007年 01月 2006年 11月 2006年 10月 2006年 09月 2006年 08月 2006年 07月 ファン
ブログジャンル
画像一覧
|
2022年 09月 04日
フロントエンドは Next.js、バックエンドの API サーバーは ASP.NET Core を用いた Web アプリケーション開発を、Visual Studio や .NET SDK を使って行なう話。 フロントエンドを、(Next.js ではなく) 素の React で作る場合は、前回の投稿でも触れたが、Visula Studio または .NET SDK のプロジェクトテンプレートから「React での ASP.NET Core」を選んで C# プロジェクト (.csproj ファイルなど) を新規作成すればよい (詳細は下記リンク先、前回の投稿を参照)。 しかしながら、Next.js については、そのようなプロジェクトテンプレートは標準で用意されていないようだ。 そこで手作業で、フロント Next.js + バックエンド ASP.NET Core の C# プロジェクトを構築することにした。 とはいえ、Next.js は "React の上に構築された" Web アプリケーション開発フレームワークということなので、「React での ASP.NET Core」を選んで作成したプロジェクトを少し改造すれば、すぐに Next.js + ASP.NET Core の C# プロジェクトに作り替えることができるだろう、と考え、プロジェクト作成の作業を開始した。 ところが大変残念なことに、この予想は大きく裏切られ、そう一筋縄ではいかなかった。 その前に: ローカル開発環境で https を必要とするか否か?先に進む前に、まず、本記事で構築する Next.js + ASP.NET Core 開発プロジェクトでは、ローカル開発サーバーへの接続にセキュア接続 (https) を必須とすることで進める。 「ローカル開発環境くらい、非セキュア接続 (http) でもよいのでは?」という判断もあろうかと思う。 しかしいっぽうで、様々なブラウザ機能のうちいくつか (例えば Web カメラからの動画入力を扱う navigator.mediaDevices.getUserMedia() とか) は、セキュア接続 (https) でないと使えなかったりする。 下記リンク先、Mozilla Developer Network の「安全なコンテキストに制限されている機能」のページに、どんな機能がセキュア接続 (="安全なコンテキスト") でのみ使える機能なのか、その一覧が掲載されている。 上記のような "セキュア接続でのみ使える" ブラウザ機能を使用している Web アプリケーションの場合は、その開発の最中においても、それら "セキュア接続でのみ使える機能" を使って動作確認したいことだろう。 そのような背景に鑑み、本記事で構築するアプリケーション開発環境では、セキュア接続 (https) 必須、つまり、開発中のローカル環境の当該 Web アプリの URL は「 https://localhost:xxxx:/ 」といった URL になる、という要件で構築することとする (この判断・方針が、あとあと自分を苦しめることになるのだが)。 開始地点さてさてまずは、Visual Studio のプロジェクト新規作成画面から「React での ASP.NET Core」を選んでプロジェクトを新規作成する。あるいは .NET CLI を使うなら、下記のようにコマンドを実行すれば、カレントフォルダ内に、"NextjsApp" というプロジェクト名で、同様のプロジェクトが構築される。 $ dotnet new react -n NextjsApp -o . こうして出来たプロジェクトファイル一式のうち、フロント側 React アプリケーションのコード類を収録している「ClientApp」フォルダをいったんバッサリを削除する。そして下記のようにコマンドを実行して、改めて Next.js プロジェクトを作り直す (下記例ではプロジェクト名を "nextjs-app" とした。"--typescript" オプションや "--use-npm" オプションを付けるかどうかはお好みで)。 $ npx create-next-app nextjs-app --typescript --use-npm 「npx create-next-app」では、プロジェクト名でサブフォルダが作成されるので、そのサブフォルダ名を「ClientApp」に名前変更し、フォルダ構成を元に戻しておく。 以上で、まずはプロジェクト構築作業の開始地点となる。 C# プロジェクト側の調整「npx create-next-app」で作られた Next.js アプリケーションは、その開発サーバーの起動コマンドは「npm run dev」ということになっており、「React での ASP.NET Core」プロジェクトにおける「npm start」とは異なる。 そこで、この C# プロジェクトファイル (.csproj) をエディタで開き、フロント側開発サーバー起動コマンドの指定である <SpaProxyLaunchCommand> MSBuild プロパティ (「npm start」が指定されている) を「npm run dev」に書き換える (下記)。 <!-- 📜 .csproj --> <Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> ... <SpaProxyLaunchCommand>npm run dev</SpaProxyLaunchCommand> ... さらには、「npm run dev」で起動する Next.js の開発サーバーは、「http://localhost:3000/ 」でリッスンするので、本当は、<SpaProxyServerUrl> MSBuild プロパティに記載するフロント側開発サーバー URL も変更する必要のが筋である。が、いったんここでは先送りする。 問題 1. Next.js 開発サーバーのプロクシ機能が自己署名証明書を拒否さて続けて、特定の URL パスへの HTTP 要求は、Next.js の開発サーバーから ASP.NET Core サーバーへ中継するよう、プロクシを構成することとした。 素の React アプリ、というか、「npx create-react-app」で生成した開発環境の場合は、「setupProxy.js」というファイル名の JavaScript ファイルを、所定のフォルダ (この場合は ./ClientApp/src ) 内に配置し、この .js ファイル内にプロクシ構成を記述することで、これを実現できた。 いっぽう、「npx create-next-app」で生成した Next.js アプリ開発環境の場合は、生成時に同時に作成されるJavaScript ファイル「next.config.js」(このケースだと ./ClientApp フォルダ内にある) を編集し、必要な記述を書き足すことで、プロクシを構成することができる (下記リンク先は公式サイトでの説明)。 イメージとしては次のような感じだ。 // 📜 next.config.js /** @type {import('next').NextConfig} */ const nextConfig = { ... async rewrites() { return [ { // 👇 この URL パスへの HTTP 要求は... source: '/weatherforecast', // 👇 こちら (ASP.NET Core サーバーの URL) に中継する。 destination: 'https://localhost:5001/weatherforecast', }, ] } } module.exports = nextConfig さて上記のような要領で「next.config.js」を構成し、動作確認した。 しかし残念なことに、ASP.NET Core サーバーへ中継される URL に HTTP 要求してみると、下記のサーバーエラーになってしまった。 Server Error Error: self signed certificate This error happened while generating the page. Any console logs will be displayed in the terminal window. Call Stack TLSSocket.onConnectSecure node:_tls_wrap (1532:34) TLSSocket.emit node:events (527:28) TLSSocket._finishInit node:_tls_wrap (946:8) TLSWrap.ssl.onhandshakedone node:_tls_wrap (727:12) どうやら self signed certificate、すなわち自己署名証明書によるセキュア接続は許容されないということらしい。 .NET SDK によって生成・インストールされる localhost 用の開発用証明書は自己署名証明書であり、それを使う ASP.NET Core サーバーへの、Next.js 開発サーバーからのプロクシ接続は拒否される、ということだ。 どうもこれは Next.js 開発環境における "仕様" ということらしい。 また、この件については GitHub 上で Issue が立てられたのだが (下記リンク先、2021年1月に起票)、 上記 Issue を確認するとわかるように、「It's a security bad practice to use self-signed certificates for handling https as well」、つまり「自己署名証明書の使用はセキュリティ上の悪しき慣行」という趣旨らしく、どうやら、自己署名証明書の使用には断固として対応しない、ということのようである。 なので、環境変数 NODE_TLS_REJECT_UNAUTHORIZED に "0" を設定しても、この制約は回避できず相変わらず上記エラーとなって終わり、である。 自己署名証明書は断固拒否は、趣旨はわからないでもない。とはいえ、素人考えとしては開発時くらいは迂回できるオプションくらいあっても良さそうにも感じた。まぁ、ここで愚痴っても仕方がない。 このような制約があるため、Next.js 開発サーバーと ASP.NET Core サーバーの通信くらいは、まぁ、非セキュア接続 (http) にしてこの問題を迂回してもよいかな、とも一瞬考えた。しかし ASP.NET Core サーバー側には、「非セキュア接続の要求は、セキュア接続へリダイレクトする」という実装が入っている。運用環境への配置時には、この ASP.NET Core サーバーがフロント側静的コンテンツもホストするパターンを想定すると、このセキュア接続への強制リダイレクト処理は捨てがたい。となると、ASP.NET Core サーバー側でそもそもいずれの通信であっても非セキュア接続を許容するように仕立てることはちょっと困難だ (いくつか手段がないわけではないけれども、手を出すにはちょっと厄介だ)。 ということで早々に詰んでしまった。 問題 2. Next.js の開発サーバーはセキュア接続をサポートしていないそしてもうひとつ、そもそも Next.js の開発サーバーは、セキュア接続をサポートしていないらしいことが、公式サイトや Stackoverflow.com をうろうろしている内にわかってきた。 「npx create-react-app」で生成した React アプリの開発環境の場合は、「.env.development」や「.env.development.local」などの環境変数設定ファイルを駆使して、下記のように環境変数を設定してやれば、その開発サーバーはセキュア接続 (https) で起動するようになっている。 HTTPS=true SSL_CRT_FILE={証明書ファイルへのパス} SSL_KEY_FILE={秘密鍵ファイルへのパス} しかしながら、Next.js の開発環境、開発サーバーには、そのような仕組みは提供されていないらしい。 ということで、そもそもの要件である「ローカル開発環境であっても、開発サーバーへの接続は、セキュア接続を必須とする」の段階で詰んでしまっていたのである。 作戦変更 - 前段にさらにリバースプロクシを立てる以上のとおり、「Next.js の開発サーバーを構成して目的を達成することは不可能」ということが判明したので、作戦変更。 もう一段、前段に、別途何かしらのリバースプロクシプロセスを立ち上げ、そのリバースプロクシで以下の様にブラウザからの HTTP 要求をさばくことにした。
さてそのリバースプロクシ実装であるが、docker でリバースプロクシを立てるなどの方法もありえる。しかし個人的にはあまり開発環境のシステム要件を増やしたくない。つまり、.NET SDK と Node.js のみを開発環境の必須要件としたく考えた (まぁ、今どきの開発環境、逆に Docker for Desktop くらいは普通はインストール済みかなぁ、とも思うのだが)。ということで、Node.js 上で動作する、npm パッケージ化されたありもののリバースプロクシ実装を選定することとした。 問題 3. 最適な npm パッケージを見つけられない早速 npmjs.com で「local proxy ssl」などのキーワードで検索。いくつか検索にヒットした中でもダウンロード数が頭ひとつ抜けて多い「local-ssl-proxy」という npm パッケージを見つけた。 ぱっと見た感じ、今回の目的に最適かも... と考えられた。しかしながら、改めてよく見ると、最終コミットが 7 年前であることに気づいた。セキュア接続を提供するためのコードが 7 年前というのは、脆弱性対策とかブラウザの互換性とか大丈夫なんだろうか、一抹の不安を覚えた。ということで採用を見送る。 その後も npmjs.com でリバースプロクシをいろいろ検索するも、機能や用途が合わなかったり、やはり最終コミットが古すぎたり、という感じで、最適な npm パッケージを発見できずに時間だけが過ぎていった。 さらに作戦変更 - リバースプロクシは自分で実装するnpmjs.com での探索もだんだん行き詰まってきたので、重ねての作戦変更を決断。すなわち、今回の用途のリバースプロクシは自分で実装することとした。 まぁ、実際、Node.js 上で動作する、express ベースのリバースプロクシプログラムは、今時代、大変ありがたいことに、「http-proxy-middleware」npm パッケージのおかげで、とっても簡単に実装することができる。 ひとたび自分で実装すると決まればあとは早い。 まずは「React での ASP.NET Core」プロジェクトテンプレートから別途生成したプロジェクトから、「aspnetcore-https.js」と「aspnetcore-react.js」の 2 つの JavaScript ファイルを拝借してくる。これら JavaScript プログラムは、
となっている。 JavaScript ファイル「aspnetcore-react.js」は、ファイル名に "react" が含まれているのがなんとなく気になって、深い意味はないのだが「aspnetcore-nextjs.js」にファイル名を変えておいた。 そして前述のとおり、リバースプロクシプログラムを、"Node.js 上で稼働する express を使った JavaScript プログラム" として実装する。 JavaScript ファイル名は「proxy-server.js」とした。 このリバースプロクシプログラムそれ自体のリッスンポート番号と、Next.js 開発環境のリッスンポート番号は、環境変数設定ファイル「.env.development」に記述し (下記例)、これを「proxy-server.js」内から参照することにした。 # .env.development PORT=44450 NEXTJS_PORT=3000 ASP.NET Core サーバーのリッスンポート番号の取得は、別途「React での ASP.NET Core」プロジェクトテンプレートから生成したプロジェクト内の「setupProxy.js」の実装を参考に転記して実装した。プロクシ機能は「http-proxy-middleware」パッケージを利用して実装する。 最終的に「proxy-server.js」の実装は 50 行にも満たずに済んだ。 // 📜 proxy-server.js const fs = require('fs'); const https = require('https'); const dotenv = require('dotenv'); const express = require('express'); const { env } = require('process'); const { createProxyMiddleware } = require('http-proxy-middleware'); dotenv.config({ path: ".env.development" }) dotenv.config({ path: ".env.development.local" }) const aspNetCoreServerUrl = env.ASPNETCORE_HTTPS_PORT ? `https://localhost:${env.ASPNETCORE_HTTPS_PORT}` : env.ASPNETCORE_URLS ? env.ASPNETCORE_URLS.split(';')[0] : 'https://localhost:7294'; const nextjsDevServerUrl = `http://localhost:${env.NEXTJS_PORT}`; const app = express(); const server = https.createServer({ cert: fs.readFileSync(env.SSL_CRT_FILE), key: fs.readFileSync(env.SSL_KEY_FILE), }, app); const context = [ "/weatherforecast", ]; // Forward to the ASP.NET Core server app.use(createProxyMiddleware(context, { target: aspNetCoreServerUrl, secure: false, headers: { Connection: 'Keep-Alive' } })); // Forward to the Next.js dev server app.use(createProxyMiddleware({ target: nextjsDevServerUrl, secure: false, headers: { Connection: 'Keep-Alive' } })); server.listen(env.PORT, () => { console.log(`Now listening on: https://localhost:${env.PORT}`); }); 仕上げ - npm-run-all パッケージを使ってプロクシと開発サーバーを並行実行以上で必要な JavaScript プログラム類の用意は完了だ。あとは、「npm run dev」を実行したときに、
の順で JavaScript プログラムを実行しつつ、さらに並行して Next.js の開発サーバーを起動するコマンド、
が実行されるように package.json 内の scripts ノードを構成すれば完成だ。 この辺の、package.json > scripts ノード内のスクリプトを、並列・順列に実行する制御のために、「npm-run-all」パッケージを利用した。 「npm-run-all」パッケージを使うことで、package.json の scripts 中で「run-p」「run-s」といったコマンドが使えるようになり、package.json の scripts 中に記述したスクリプトを、「run-p」なら並列実行、「run-s」なら順列実行することができるようになる。 ということで、package.json の scripts ノードは以下のとおりとなった。 // 📜 package.json { ... "scripts": { "dev": "run-p nextdev proxy-server", "nextdev": "next dev", "proxy-server": "node aspnetcore-https && node aspnetcore-nextjs && node proxy-server", ... 完成!これで Visual Studio から Ctrl + F5 や F5 で実行、ないしは .NET CLI なら「dotnet run」を実行することで、ASP.NET Core サーバー、Next.js 開発サーバー、自作のリバースプロクシプログラム、のそれぞれが起動するようになった。ASP.NET Core サーバーおよびリバースプロクシプログラムは、.NET SDK による開発サーバー用自己署名証明書を使ったセキュア接続 (https) で待ち受けする。 Visual Studio からの実行の場合は、さらにブラウザが起動して、リバースプロクシプログラムのリッスンポート番号の URL を開くようになっている (※厳密には、いったん ASP.NET Core サーバーの URL をブラウザで開き、その ASP.NET Core サーバーからの応答コンテンツにて、リバースプロクシプログラムの URL へリダイレクトする)。 ![]() 以上の手順を再現したコミット履歴およびプロジェクトファイル一式は、下記 GitHub リポジトリで公開してある。 おまけ自作のリバースプロクシプログラムは、今回は JavaScript で実装したが、開発環境の縛りが「.NET SDK」と「Node.js」なので、C# で "YARP" (.NET 用のリバースプロクシライブラリ) を使って書いてもよかったかもな、と考えたりしている。
by developer-adjust
| 2022-09-04 16:56
| .NET
|
Comments(0)
|
ファン申請 |
||