おそらく、コード レベルからイントラネット侵入を説明する記事をインターネットで見つけるのは難しいでしょう。私は探しましたが見つからなかったので、この記事を書きました。 1. LAN内のプロキシまず前回の記事を見直してみましょう。ローカル エリア ネットワーク内でサービス プロキシを実装するにはどうすればよいでしょうか。これは非常に単純なので、すぐにコードを見てみましょう。 定数net = require('net') const proxy = net.createServer(socket => { const localServe = 新しい net.Socket() localServe.connect(5502, '192.168.31.130') // LAN 内のサービス ポートと IP。 socket.pipe(localServe).pipe(ソケット) }) プロキシ.listen(80) これは非常にシンプルなサーバー側プロキシです。コードはシンプルで明確です。質問がある場合は、おそらくここのパイプです。簡単に説明しましょう。ソケットは全二重ストリーム、つまり読み取りと書き込みの両方が可能なデータ ストリームです。コードでは、ソケットがクライアントからデータを受信すると、そのデータを localSever に書き込みます。localSever にデータがある場合、そのデータがソケットに書き込まれ、ソケットはそのデータをクライアントに送信します。 2. イントラネットの浸透LAN プロキシは簡単ですが、イントラネットへの侵入はそれほど簡単ではありません。ただし、これはコアコードであり、かなりの論理処理が必要です。具体的に実装する前に、まずイントラネットの浸透度を整理しましょう。 イントラネット侵入とは何ですか?簡単に言えば、ローカル エリア ネットワーク内のサービスにアクセスできるパブリック ネットワーク クライアントです。たとえば、サービスはローカルで開始されました。パブリック ネットワーク クライアントは、ローカルで開始されたサーバーをどのようにして認識するのでしょうか?ここでは、パブリック ネットワーク サーバーに依存する必要があります。では、パブリック ネットワーク サーバーはどのようにしてローカル サービスを認識するのでしょうか?これには、ローカルとサーバー間のソケット リンクを確立する必要があります。 4つの役割 上記の説明を通じて、4つの役割を紹介します。
このうち、クライアントと localServe については考慮する必要はありません。クライアントはブラウザなどであり、localServe は通常のローカル サービスにすぎないからです。注意する必要があるのは proxyServe と bridge だけです。ここで紹介するのは、やはり最もシンプルな実装方法であり、考え方や考え方を提供するものなので、まずは最もシンプルなものから始めましょう。 橋4 つの役割のセクションから、ブリッジは proxyServe とのソケット接続であり、データの転送であることがわかります。コードを見て、アイデアを整理してみましょう。 定数net = require('net') 定数 proxyServe = '10.253.107.245' const ブリッジ = 新しい net.Socket() ブリッジ.connect(80, proxyServe, _ => { bridge.write('GET /regester?key=sq HTTP/1.1\r\n\r\n') }) ブリッジ.on('データ', データ => { const localServer = 新しい net.Socket() localServer.connect(8088, 'localhost', _ => { localServer.write(データ) localServer.on('data', res => bridge.write(res)) }) }) コードは明確で読みやすく、印象に残ります。ネットライブラリをインポートし、パブリックネットワークアドレスを宣言し、ブリッジを作成し、ブリッジを proxyServe に接続し、成功したら、ローカルサービスを proxyServe に登録します。次に、ブリッジはデータをリッスンし、リクエストが到着すると、ローカルサービスとの接続を作成します。成功したら、リクエストデータは localServe に送信され、同時にレスポンスデータがリッスンされ、レスポンスストリームがブリッジに書き込まれます。 残りについてはあまり説明する必要がありません。結局のところ、これは単なるサンプル コードです。ただし、サンプル コードには /regester?key=sq というセクションがあります。このキーは非常に便利です。ここでは key=sq です。次に、ロール クライアントがプロキシ サービスを介してローカル サービスにアクセスするときに、proxyServe がブリッジに対応し、localServe に対応できるように、このキーをパスに追加する必要があります。 たとえば、lcoalServe は http://localhost:8088、rpoxyServe は example.com、登録キーは sq です。次に、prxoyServe を介して localServe にアクセスする場合は、次のように記述する必要があります: example.com/sq。なぜこのように書くのでしょうか?もちろん、これは単なる定義です。この記事のコードを理解したら、この規則を変更できます。 それでは、次のキーコードを見てみましょう。 プロキシサーブここで紹介する proxyServe は簡略化されたサンプルコードではありますが、説明するのはまだ少し複雑です。十分に理解し、自分の業務と組み合わせて使えるコードにするには、ある程度の努力が必要です。ここではコードをいくつかの部分に分割して、わかりやすく説明します。説明を容易にするために、コード ブロックに名前を付けます。 このブロックの主な機能は、プロキシ サービスを作成し、クライアントとブリッジとのソケット リンクを確立し、ソケット上のデータ要求をリッスンし、コールバック関数で論理処理を実行することです。具体的なコードは次のとおりです。 定数net = require('net') const bridges = {} // ブリッジがソケット接続を確立すると、ここにキャッシュされます。const clients = {} // クライアントがソケット接続を確立すると、ここにキャッシュされます。具体的なデータ構造については、ソースコードを参照してください。net.createServer(socket => { socket.on('data', データ => { const リクエスト = data.toString() const url = request.match(/.+ (?<url>.+) /)?.groups?.url if (!url) 戻り値 if (isBridge(url)) { regesterBridge(ソケット、URL) 戻る } const { ブリッジ、キー } = findBridge(リクエスト、URL) if (!bridge) 戻り値 cacheClientRequest(ブリッジ、キー、ソケット、リクエスト、URL) キーによるブリッジへのリクエストの送信(キー) }) }).listen(80) データ モニターのコード ロジックを見てみましょう。
コードとロジックを組み合わせると、理解できるはずですが、5については疑問が残るかもしれません。1つずつ整理してみましょう。 コードブロック 2: isBridge ブリッジ登録リクエストであるかどうかを判断する方法は非常に簡単ですが、実際のビジネスでは、より正確なデータが定義される場合があります。 関数isBridge(url){ url.startsWith('/regester?') を返します } コードブロック 3: regesterBridge 関数regesterBridge(ソケット、URL){ 定数キー = url.match(/(^|&|\?)key=(?<key>[^&]*)(&|$)/)?.groups?.key ブリッジ[キー] = ソケット socket.removeAllListeners('データ') }
コードブロック 4: findBridge ロジックがコード ブロック 4 に到達すると、これはすでにクライアント要求であることを意味します。次に、まず対応するブリッジを見つける必要があります。ブリッジがない場合は、まずブリッジを登録する必要があり、その後、ユーザーはクライアント要求を開始する必要があります。コードは次のとおりです。 関数 findBridge (リクエスト、URL) { key = url.match(/\/(?<key>[^\/\?]*)(\/|\?|$)/)?.groups?.key とします。 bridge = bridges[キー]とします if (bridge) return { bridge, key } const referer = request.match(/\r\nReferer: (?<referer>.+)\r\n/)?.groups?.referer if (!referer) が {} を返す キー = referer.split('//')[1].split('/')[1] bridge = 橋[キー] if (bridge) return { bridge, key } 戻る {} }
コードブロック 5: cacheClientRequest ここでのコード実行は、それがすでにクライアント リクエストであることを示しています。まず、このリクエストをキャッシュします。キャッシュするときに、後続の操作を容易にするために、リクエストに対応するブリッジとキー バインディングもキャッシュします。 クライアント要求をキャッシュする理由は何ですか? 現在のソリューションでは、リクエストとレスポンスの両方がペアで順序付けられることを期待しています。ネットワーク伝送が断片化していることは周知の事実です。現状では、アプリケーション層でリクエストとレスポンスをペアにして順序付けて制御しないと、データパケット間の混乱が生じてしまいます。これが現状です。将来的にもっと良い解決策が見つかったら、アプリケーション層でデータの要求と応答を強制的に順序通りにするのではなく、TCP/IP 層を信頼することができます。 関数cacheClientRequest(ブリッジ、キー、ソケット、リクエスト、URL){ if (クライアント[キー]) { クライアント[キー].requests.push({ブリッジ、キー、ソケット、リクエスト、URL}) } それ以外 { クライアント[キー] = {} クライアント[キー].requests = [{ブリッジ、キー、ソケット、リクエスト、URL}] } } まず、ブリッジに対応するキーの下にクライアント要求キャッシュがすでに存在するかどうかを判断します。存在する場合は、それをプッシュします。 そうでない場合は、オブジェクトを作成し、このリクエストを初期化します。 次のステップは最も複雑で、リクエスト キャッシュを取り出してブリッジに送信し、現在の応答が終了するまでブリッジの応答をリッスンします。次に、ブリッジのデータ監視を削除し、次のリクエストを取り出すことを試み、クライアントからのすべてのリクエストが処理されるまで上記のアクションを繰り返します。 コードブロック 6: sendRequestToBridgeByKey コード ブロック 5 の最後に、ブロックの概要説明が示されます。まず少し理解してから次のコードを見てください。コードにはレスポンスの整合性の判断がいくつかあるからです。これらを削除すると、コードが理解しやすくなります。ソリューション全体では、リクエストの整合性は処理しませんでした。これは、ファイル アップロード インターフェイスでない限り、リクエストは基本的にデータ パケットのサイズ内であるためです。ファイル アップロード インターフェイスは現時点では処理しません。そうでない場合、コードがより複雑になります。 関数 sendRequestToBridgeByKey (キー) { const client = クライアント[キー] if (client.isSending) 戻り値 const リクエスト = client.requests if (requests.length <= 0) 戻り値 client.isSending = true クライアント.コンテンツ長 = 0 クライアント受信 = 0 const {ブリッジ、ソケット、リクエスト、URL} = リクエスト.shift() const newUrl = url.replace(キー、'') const newRequest = request.replace(url, newUrl) bridge.write(新しいリクエスト) ブリッジ.on('データ', データ => { 定数レスポンス = data.toString() code = response.match(/^HTTP[S]*\/[1-9].[0-9] (?<code>[0-9]{3}).*\r\n/)?.groups?.code とします。 if (コード) { コード = parseInt(コード) (コード === 200) の場合 { contentLength = response.match(/\r\nContent-Length: (?<contentLength>.+)\r\n/)?.groups?.contentLength とします。 if (コンテンツの長さ) { コンテンツの長さ = parseInt(コンテンツの長さ) client.contentLength = コンテンツの長さ client.received = Buffer.from(response.split('\r\n\r\n')[1]).length } } それ以外 { socket.write(データ) client.isSending = false bridge.removeAllListeners('データ') キーによるブリッジへのリクエストの送信(キー) 戻る } } それ以外 { クライアントが受信したデータ += データの長さ } socket.write(データ) (client.contentLength <= client.received) の場合 { client.isSending = false bridge.removeAllListeners('データ') キーによるブリッジへのリクエストの送信(キー) } }) } クライアントの中からブリッジキーに対応するクライアントを取り出します。
この時点で、コアコードロジックは完成しました。 要約するこのコード セットを理解した後は、それを拡張して独自の用途に合わせてコードを充実させることができます。このコードセットを理解した後、他の使用シナリオを思いつくことができますか?このアイデアはリモート コントロールにも使用できますか? クライアントを制御したい場合は、このコードからインスピレーションを得ることができます。 proxyServe ソースコード定数net = require('net') const ブリッジ = {} 定数クライアント = {} net.createServer(ソケット => { socket.on('data', データ => { const リクエスト = data.toString() const url = request.match(/.+ (?<url>.+) /)?.groups?.url if (!url) 戻り値 if (isBridge(url)) { regesterBridge(ソケット、URL) 戻る } const { ブリッジ、キー } = findBridge(リクエスト、URL) if (!bridge) 戻り値 cacheClientRequest(ブリッジ、キー、ソケット、リクエスト、URL) キーによるブリッジへのリクエストの送信(キー) }) }).listen(80) 関数isBridge(url){ url.startsWith('/regester?') を返します } 関数regesterBridge(ソケット、URL){ 定数キー = url.match(/(^|&|\?)key=(?<key>[^&]*)(&|$)/)?.groups?.key ブリッジ[キー] = ソケット socket.removeAllListeners('データ') } 関数 findBridge (リクエスト、URL) { key = url.match(/\/(?<key>[^\/\?]*)(\/|\?|$)/)?.groups?.key とします。 bridge = bridges[キー]とします if (bridge) return { bridge, key } const referer = request.match(/\r\nReferer: (?<referer>.+)\r\n/)?.groups?.referer if (!referer) が {} を返す キー = referer.split('//')[1].split('/')[1] bridge = 橋[キー] if (bridge) return { bridge, key } 戻る {} } 関数cacheClientRequest(ブリッジ、キー、ソケット、リクエスト、URL){ if (クライアント[キー]) { クライアント[キー].requests.push({ブリッジ、キー、ソケット、リクエスト、URL}) } それ以外 { クライアント[キー] = {} クライアント[キー].requests = [{ブリッジ、キー、ソケット、リクエスト、URL}] } } 関数 sendRequestToBridgeByKey (キー) { const client = クライアント[キー] if (client.isSending) 戻り値 const リクエスト = client.requests if (requests.length <= 0) 戻り値 client.isSending = true クライアント.コンテンツ長 = 0 クライアント受信 = 0 const {ブリッジ、ソケット、リクエスト、URL} = リクエスト.shift() const newUrl = url.replace(キー、'') const newRequest = request.replace(url, newUrl) bridge.write(新しいリクエスト) ブリッジ.on('データ', データ => { 定数レスポンス = data.toString() code = response.match(/^HTTP[S]*\/[1-9].[0-9] (?<code>[0-9]{3}).*\r\n/)?.groups?.code とします。 if (コード) { コード = parseInt(コード) (コード === 200) の場合 { contentLength = response.match(/\r\nContent-Length: (?<contentLength>.+)\r\n/)?.groups?.contentLength とします。 if (コンテンツの長さ) { コンテンツの長さ = parseInt(コンテンツの長さ) client.contentLength = コンテンツの長さ client.received = Buffer.from(response.split('\r\n\r\n')[1]).length } } それ以外 { socket.write(データ) client.isSending = false bridge.removeAllListeners('データ') キーによるブリッジへのリクエストの送信(キー) 戻る } } それ以外 { クライアントが受信したデータ += データの長さ } socket.write(データ) (client.contentLength <= client.received) の場合 { client.isSending = false bridge.removeAllListeners('データ') キーによるブリッジへのリクエストの送信(キー) } }) } これで、Nodejs によるイントラネット侵入サービスの実装に関するこの記事は終了です。より関連性の高い Node イントラネット侵入コンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。 以下もご興味があるかもしれません:
|
<<: MySQL サーバー 5.7.20 のインストールと設定方法のグラフィック チュートリアル
>>: Docker を使用した MySQL のデプロイの詳細説明 (データ永続化)
序文CSS で水平方向と垂直方向に中央揃えする方法はたくさんあります。この記事で紹介する方法は非常に...
問題: mybatis によって返される null 型のデータが消え、フロントエンドの表示にエラーが...
目的リクエスト アクセス ボリュームを制御するための Nginx ngx_http_limit_co...
OBS studioかっこいいですが、 JavaScriptもっとかっこいいです。では、 JavaS...
序文ソースコードは合計で 100 行強しかありません。これを読めば、react-dnd などの成熟し...
WindowsとLinux間のファイル転送(1)WinSCPを使用して、WindowsファイルをLi...
目次1. コンポーネント2. キープアライブ2.1 問題点2.2 キープアライブを使って解決する2....
この記事では、参考までに、echartを使用してタグと色をカスタマイズするVueの具体的なコードを紹...
目次イベントページの読み込みイベント委任イベントの切り替えイベント要約するイベントページの読み込み1...
目次序文1. 例で理解する2. ソースコードを分析する3. まとめ要約する序文他の人のコンポーネント...
目次例方法1: 削除方法2: 分解補充する要約するThinking シリーズは、10 分で実用的なプ...
当銀行のMGRは年末に開始されます。公式文書を読んだり、毎日テストを受けたりしなければなりません。毎...
純粋なCSS3で蝶が羽ばたく様子を再現。まずはその効果をご覧ください どうですか?効果はかなりいいで...
関連記事:初心者が学ぶ HTML タグ (5)導入された HTML タグは、必ずしも XHTML 仕...
プロファイルを使用して遅いSQLを分析するMySQL の SQL パフォーマンス アナライザーの主な...