Aprende a crear juegos en HTML5 Canvas

domingo, 17 de noviembre de 2013

Creando un servidor

En la semana pasada, instalamos y probamos Node.js. Ahora, aprenderemos a crear un servidor básico para entregar nuestros archivos.

Antes de pasar al desarrollo de juegos MMO, necesitamos que Node.js entregue los archivos necesarios para cargarlo. Esto es, las imágenes, los sonidos, el código, ¡Incluso la misma página inicial! Para poder hacer esto, necesitaremos modificar nuestra función "MyServer" para que se encargue de ello. Antes que nada, comenzaremos importando una nueva librería, que nos permitirá leer los archivos del servidor:
    var fs = require('fs');
Ahora sí, comenzaremos modificando nuestra función "MyServer". La vez pasada, vimos que esta función recibe dos variables: "request" y "response". También aprendimos a usar "response" para crear respuestas básicas, lo que nos permitió imprimir un "Hello Node.js". Ahora aprenderemos a usar la otra variable, "request". Esta nos permite conocer las peticiones que se hicieron al servidor al llamar a esta función. En este caso, le usaremos para saber que archivo nos está pidiendo que sea servido:
    function MyServer(request, response) {
        var filePath = '.' + request.url;
"request.url" nos dará el archivo solicitado con su ruta completa. La concatenamos con un punto al inicio, para que sus llamadas sean desde la carpeta actual donde han sido solicitadas. Posteriormente, identificaremos si ningún archivo en especial ha sido pedido al servidor ("./"), y de ser así, le entregaremos el archivo "./index.html", que es nuestra página de inicio:
        if (filePath === './') {
            filePath = './index.html';
        }
Posteriormente, para saber que tipo de archivo vamos a entregar, obtendremos la extensión de su nombre con la siguiente línea:
    var extname = filePath.substr(filePath.lastIndexOf('.'));
Para obtener el tipo del archivo, lo compararemos con una lista almacenada en un objeto de diccionario, la cual declararemos al comienzo:
    var contentTypes = {
            ".html": "text/html",
            ".css": "text/css",
            ".js": "application/javascript",
            ".png": "image/png",
            ".jpg": "image/jpeg",
            ".ico": "image/x-icon",
            ".m4a": "audio/mp4",
            ".oga": "audio/ogg"
        };
Como podrás ver, esta lista ya contiene los tipos de archivos comunes que usamos para nuestros juegos: archivos HTML, hojas de estilo en cascada (Que no hemos visto ni veremos en este blog, pero son muy importantes en el desarrollo web para la imagen del sitio), los códigos de JavaScript, imágenes en formato PNG y JPG, íconos como los favicon, que son la imagen que suele aparecer en la pestaña de nuestra página (Y que tampoco hemos visto en el blog), y por supuesto, audio en formato m4a y oga. Si necesitas agregar otro tipo de extensión a la lista, solo usa tu buscador web favorito con la palabras "MIME type", acompañado de la extensión del archivo que requieres.

Ahora sí, sigamos con la función de "MyServer". Para obtener el tipo de contenido correcto, de acuerdo a su extensión y la lista anterior, es tan sencillo como la línea mostrada a continuación:
        extname = filePath.substr(filePath.lastIndexOf('.'));
        contentType = contentTypes[extname];
Después, verificaremos si no se ha asignado un tipo de contenido (Esto es, que el archivo solicitado no esté en la lista), y de ser así, lo serviremos como "application/octet-stream", que se asigna a cualquier archivo en la web desconocido, y el cual lo entregará como un archivo binario, listo para descargar:
        if (!contentType) {
            contentType = 'application/octet-stream';
        }
Finalmente, imprimiremos en la consola una nota con la fecha y hora actual, y el archivo que ha sido solicitado, antes de continuar con su procesamiento:
        console.log((new Date()) + ' Serving ' + filePath + ' as ' + contentType);
Ahora que ya tenemos los datos del archivo solicitado, vamos a intentar entregarlo. Para eso, usaremos la librería que importamos desde el principio, y le preguntaremos si el archivo existe. Esta nos responderá con una función, que contendrá una variable, en la que nos indicará si el archivo existe o no:
        fs.exists(filePath, function (exists) {
            
        });
Si existe, intentaremos leer dicho archivo con la misma librería. Esta nos volverá a responder con una función, la cual nos entregará si hubo un error mientras se leía el archivo, y el contenido del mismo:
            if (exists) {
                fs.readFile(filePath, function (error, content) {
                
                });
            }
Si hubo un error durante la lectura del archivo, responderemos que hubo un error en el servidor, mandando en la cabecera el tipo de error correspondiente (500):
                    if (error) {
                        response.writeHead(500, { 'Content-Type': 'text/html' });
                        response.end('

500 Internal Server Error

'); }
Por el contrario, si no hubo error alguno, responderemos en la cabecera que todo está bien (200), y entregaremos el contenido del archivo:
                    } else {
                        response.writeHead(200, { 'Content-Type': contentType });
                        response.end(content, 'utf-8');
                    }
Con esto nuestro servidor ya podrá entregar archivos de forma correcta. Solo nos queda responder en caso de que el archivo no exista, con un tipo de error que estoy seguro todos nos habremos encontrado al menos en alguna ocasión, mientras navegamos en Internet:
            } else {
                response.writeHead(404, { 'Content-Type': 'text/html' });
                response.end('

404 Not Found

'); }
De esta forma, nuestro servidor Node.js está listo para entregar nuestros archivos a los usuarios. Para comprobar que todo funciona de forma perfecta, prueba agregar uno de los juegos que hayas desarrollado previamente a la carpeta, y juégalo desde el sitio que se genera al activar tu servidor de Node.js.

Código final:

server.js
/*global console, process, require */
(function () {
    'use strict';
    var serverPort = 5000,
        server = null,
        fs = require('fs'),
        
        contentTypes = {
            ".html": "text/html",
            ".css": "text/css",
            ".js": "application/javascript",
            ".png": "image/png",
            ".jpg": "image/jpeg",
            ".ico": "image/x-icon",
            ".m4a": "audio/mp4",
            ".oga": "audio/ogg"
        };

    function MyServer(request, response) {
        var filePath = '.' + request.url,
            extname = '',
            contentType = '';
        if (filePath === './') {
            filePath = './index.html';
        }

        extname = filePath.substr(filePath.lastIndexOf('.'));
        contentType = contentTypes[extname];
        if (!contentType) {
            contentType = 'application/octet-stream';
        }
        console.log((new Date()) + ' Serving ' + filePath + ' as ' + contentType);

        fs.exists(filePath, function (exists) {
            if (exists) {
                fs.readFile(filePath, function (error, content) {
                    if (error) {
                        response.writeHead(500, { 'Content-Type': 'text/html' });
                        response.end('<h1>500 Internal Server Error</h1>');
                    } else {
                        response.writeHead(200, { 'Content-Type': contentType });
                        response.end(content, 'utf-8');
                    }
                });
            } else {
                response.writeHead(404, { 'Content-Type': 'text/html' });
                response.end('<h1>404 Not Found</h1>');
            }
        });
    }

    server = require('http').createServer(MyServer);
    server.listen(serverPort, function () {
        console.log('Server is listening on port ' + serverPort);
    });
}());

3 comentarios:

  1. Excelente!! Muchas gracias por éstos tutoriales son geniales :D

    ResponderEliminar
  2. Sencillo y robusto, ¿pero no crees un poco de expressjs nos permitiria abstraernos de este tipo de banalidades?

    ResponderEliminar
    Respuestas
    1. Posiblemente, ¿Pero por qué habría de instalarse una librería tan robusta, que requiere su propio mantenimiento, si en realidad necesitamos algo tan sencillo como es servir la vista de un juego? Express serviría mejor si deseas montar tu sitio completo en Node.

      Además, la práctica más común es usar un servidor distinto (En Python, PHP o similar) para servir las vistas, y la conexión en tiempo real se hace aparte en Node, por lo que comúnmente esto ni siquiera es requerido en la mayoría de las apps profesionales. Pero dado que es muy complejo enseñar como instalar dos servidores, el tutorial cubre mejor esta forma por ser más sencillo para aprender.

      Eliminar