プログラマーが何もすることがないとき、彼はGopherサーバーを書きます

前の考古学的投稿の著者がHabrのGopher Weekの魔神を解放しなかったことを願っています。 私もこれをやりたくはありませんが、このトピックが取り上げられたので、私は自分の魂に罪の一部を持ち込むことを敢えてします。



JSでの140行のGopherサーバーの実装例。



背景のビット。 少し前に、私は本当に何もすることが全くありませんでした.Node.jsの部門内セミナーの準備の一環として、私は、野田のような超現代的でトレンディなものについて、善の名の下に皆が忘れていた古代のプロトコルを実装することで、頭を少し伸ばすことにしました。 私が最初に選んだのはIRCでしたが、すべてのRFCを読み、その場でいくつかの実装を見た後、何かをひねりました。 セミナーまであと1週間しか残っていませんでしたが、この間に多少動作するIRCサーバーを書くことは、非現実的であるだけでなく、明らかに問題があるように思えました。



おそらく、現在の歴史的文脈におけるGopherの唯一の利点は、その驚くべき単純さです。 見てください、 RFC1436はIETFの標準では少し短いです。 ウィキペディアの記事はさらに短くなっています。 これで十分です。



したがって、独自のダムGopherサーバーを準備するには、次の要素が必要です。







まず、リスナーのソケットにハングアップします。これは、クライアントがCRLFで終わる行を送信するまで待機します。その後、要求に応答するか、NULLを返さなければなりません。その後、接続を閉じる必要があります。



var server = net.createServer(function (sock) { var query = ""; console.log('Client connected from ' + sock.remoteAddress + ' port ' + sock.remotePort); sock.on('end', function () { console.log('Client disconnected'); }); sock.on('data', function (buf) { console.log('Received ' + buf.length + ' byte(s) of data'); var r = false; for (var i = 0; i < buf.length; i++) { var b = buf.readUInt8(i); switch (b) { case 0x0: r = false; return; case 0xD: r = true; break; case 0xA: if (r) { handleQuery(query, sock); } break; default: r = false; query += String.fromCharCode(b); } } }); });
      
      







要求に答える必要がある場合は、文字列が空かどうかを確認します。 空の場合、現在のディレクトリのリストを含むメニュー(Gopherはテキストメニューベースのプロトコルで、フィールドはタブで区切られています)で応答します。 そうでない場合は、要求されたリソースのタイプに応じて、そのコンテンツを提供するか、全文検索を行います。 全文クエリでは、タブ文字とその存在を必ず確認し、最初にチェックします。



 function handleQuery(query, sock) { var paramPos = query.indexOf(TAB); if (paramPos > -1) { var search = query.substr(paramPos + 1); query = query.substr(0, paramPos); var path = fs.realpathSync(query == '' ? ROOT_DIR + '/' : ROOT_DIR + query); console.log('Handling search query ' + search + ' in the path ' + query); answerInfo(sock, 'Search results for query ' + search + ' in current directory and all subdirectories:'); printList(sock, path, query, indexer.searchFor(path, search)); } else { var path = fs.realpathSync(query == '' ? ROOT_DIR + '/' : ROOT_DIR + query); console.log('Handling path query ' + path); fs.exists(path, function (exists) { if (!exists) { answerError(sock, 'File ' + path + " doesn't exists"); return; } }); fs.stat(path, function (err, stats) { if (stats.isDirectory()) { answerDirList(sock, query, path); } else { fs.readFile(path, function (err, data) { sock.end(data); }); } }); } }
      
      







リスト自体はファイルのMIMEタイプに基づいて生成され、対応するマジック定数を置き換えます。



  function printList(sock, path, query, entries) { var answer = ""; if (entries.length == 0) { answerInfo(sock, 'Nothing to display here'); } else { for (var i = 0; i < entries.length; i++) { var entry = entries[i]; var stat = fs.statSync(path + '/' + entry); if (stat.isDirectory()) { answer += "1"; } else { var mt = mime.lookup(entry); if ((mt.indexOf('text/html') == 0) || (mt.indexOf('application/xhtml+xml') == 0)) { answer += 'h'; } else if (mt.indexOf('uue') > -1) { answer += '6'; } else if (mt.indexOf('text/') == 0) { answer += '0'; } else if (mt.indexOf('image/gif') == 0) { answer += 'g'; } else if (mt.indexOf('image/') == 0) { answer += 'I'; } else if (mt.indexOf('audio/') == 0) { answer += 's'; } else if (mt.indexOf('binhex') > -1) { answer += '4'; } else if ((mt.indexOf('compressed') > -1) || (mt.indexOf('archive') > -1)) { answer += '5'; } else { answer += '9'; } } answer += entry + TAB + query + '/' + entry + TAB + SERVER + TAB + PORT + "\r\n"; } } answer += '7Search in this directory and all subdirectories...' + TAB + query + TAB + SERVER + TAB + PORT + "\r\n"; answer += EOF; sock.end(answer); }
      
      







ほんの少しのバインディングコードです。Node.jsのような高レベルの最新フレームワークでは、前世紀に時代遅れになったプロトコルの実装は、実際には約2時間半の子供の仕事であると確信しています。 この不名誉をすべてスライドの形で(もっと時間がかかった)、セミナーに行って、拍手と大きな拍手を破ります。



そしてこれが利益です。



はい、ほとんど忘れていました。 クライアントも必要なため、サーバーを作成するだけでは不十分です。 私たちは、すべての最新のブラウザが約100年前に(良い名義で)Suslikのサポートを取り除いたことを確認していますが、Firefoxのプラグインを書いた愛好家は世界に残りました。 OverbiteFFおよびデバッグ。



実際、すべてのコードは完全にGitHubのプロジェクトの形式になっています 。 誰かがタイプ8のサポートを作成できる場合は、プルリクエストを送信してください。



All Articles