📜 ⬆️ ⬇️

When a programmer has nothing to do, he writes a Gopher-server

I hope the author of the previous archaeological post did not release the Gin of the Week of Gopher to Habr. I also do not want to do this, but since the topic has been raised, I dare to take part of the sin for my soul.

An example of the implementation of the Gopher server in 140 lines on JS.

A little background. Some time ago I really had absolutely nothing to do and as part of preparing the internal Node.js seminar I decided to slightly stretch the brain with the implementation of some ancient, forgotten by everyone in the name of good, protocol for such an ultramodern and trendy thing like Noda. Initially, my choice was on IRC, but after reading all the RFCs and looking at a couple of implementations on this, something I curled up. There was only a week left before the seminar, and writing a working IRC server during this time seemed to me not so unrealistic, but obviously problematic.
')
Perhaps the only advantage of Gopher in the current historical context is its amazing simplicity. See, RFC1436 is just a shorty by IETF standards. The Wikipedia article is even shorter. And this is quite enough.

So, to make your own stupid Gopher server, we need the following ingredients.



First of all, we hang on the socket of the listener, which will wait until the client sends us a string ending in CRLF - then we must respond to the request, or NULL - then we must close the connection:

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); } } }); }); 


If we need to respond to a request, then we see if the string was empty. If empty, we answer with a menu (and Gopher is a text-based menu-based protocol, the fields of which are separated by tabs) containing a listing of the current directory. If not, depending on the type of resource requested, either we give its content or we do a full-text search. In the full-text query, we must meet the tab character, its presence and check it first.

 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); }); } }); } } 


We generate the listing itself based on the mime type of the files, substituting the corresponding magic constants.

  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); } 


A little more binding code, and we are convinced that for such a high-level modern framework as Node.js, the implementation of some protocol that was outdated in the last century is indeed a child’s task for about two and a half hours. We make all this ugliness in the form of slides (which took more time), go to the seminar, break the applause and loud applause.

And in this profit.

Yes, I almost forgot. It's not enough to write a server, because you also need a client. We are convinced that all modern browsers got rid of the support of Gopher about a hundred and ten years ago (in the name of good), but the enthusiasts who wrote a plugin for Firefox remained in the world. On OverbiteFF and debugging.

Actually, all code is completely in the form of a project on GitHub . If someone is able to write type 8 support, please send a request.

Source: https://habr.com/ru/post/238805/


All Articles