PHP Avanzado

Aprende a usar el servidor web interno de PHP

|

Desde la versión 5.4 de PHP, contamos con una nueva herramienta en nuestro arsenal: un Servidor Web interno en PHP, que nos permite realizar pruebas de una manera sencilla, sin la necesidad de tener un servidor web (ya sea NginX o Apache) corriendo de manera separada.

En este artículo voy a intentar explicar brevemente cómo funciona este servidor web, que opciones tenemos para configurarlo, y finalmente, les mostraré un ejemplo sobre como podemos crear un pequeño proyecto.

Comencemos por lo básico

El servidor Web se ejecuta desde línea de comandos, ya sea en una consola (Mac/Linux) o desde PowerShell (Windows). Para verificar que ciertamente tenemos una versión igual o superior a 5.4, deberíamos ejecutar php -v y comprobar esto.

Primero, creemos un archivo info.php que ejecute una llamada a phpinfo() y guardémoslo en un directorio vacío.

info.php
1
<?php phpinfo(); ?>

A continuación, ya podemos ejecutar el servidor web, que de la manera más básica, se inicia de esta manera

1
$ php -S localhost:8000

Esto iniciará el servidor web, utilizando como raíz el directorio en el que nos encontramos actualmente, y publicando el servicio en la dirección localhost y en el puerto 8000.

1
php -S localhost:8000
PHP 5.5.20 Development Server started at Thu Jul 16 23:13:59 2015
Listening on http://localhost:8000
Document root is /var/www/phpservertest
Press Ctrl-C to quit.

Efectivamente, si abrimos un navegador web, y nos dirigimos a http://localhost:8000/info.php podemos acceder a la información de phpinfo() y comprobar que todo está funcionando correctamente.

Opciones de la línea de comandos.

Como ya vimos, la opción -S es la que realiza toda la magia y lanza el servidor web, pero hay ciertas opciones que nos permiten modificar este comportamiento.

Si ejecutamos el servidor con la opción -t, podríamos indicarle otro directorio como el directorio raiz del servidor, efectivamente pudiendo soportar cualquier directorio en nuestro disco rígido.

1
php -S localhost:8000 -t ./public_html
PHP 5.5.20 Development Server started at Thu Jul 16 23:25:15 2015
Listening on http://localhost:8000
Document root is /var/www/phpservertest/public_html
Press Ctrl-C to quit.

Una opción útil en este caso, que no es propia del servidor web sino que existe hace mucho tiempo en el intérprete de PHP, es -c que nos permite indicar un archivo de configuración distinto al default. Así, si tuviéramos dentro de nuestra aplicación, un archivo temporal de configuración, podríamos crear un ambiente de testing con distinta configuración a un ambiente de producción:

1
php -S localhost:8000 -c etc/dev.ini

Por último, el servidor web acepta como parámetro un archivo.php, pero al contrario de lo que pensaríamos, no lo ejecuta directamente como el archivo raiz de la aplicación. Este archivo.php es considerado como un Router, que se ejecutará automáticamente antes de cada petición que realicemos hacia cualquier recurso.

Si este archivo retorna false entonces el servidor web procesará ese archivo normalmente. Si este archivo retorna cualquier otra cosa que no sea false entonces ese contenido será lo que el servidor responde, independientemente del recurso que se intente acceder via web.

Volviendo al ejemplo anterior donde mostrábamos el resultado del archivo info.php si accedíamos mediante un navegador a http://localhost:8000/info.php, veamos que sucede si creamos un archivo router.php que se ejecute antes de cada petición.

router.php
1
2
3
4
5
6
7
8
<?php
$path = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
if ($path === '/info.php') {
echo "Nah, nah, ya no puedes ver este archivo";
return true;
}
return false;
?>

Ya no podemos acceder a la información dentro de info.php.

De esta manera, podemos controlar brevemente que tipos de archivos se pueden ejecutar en nuestro servidor web de desarrollo, o podemos realizar otros tipos de lógica, como indicar que parte de un sitio se cargará si intentamos acceder a distintas urls. Este trabajo generalmente se llevaría a cabo en un servidor web como Apache dentro del archivo .htaccess o en NginX dentro de nginx.conf.

Un proyecto de ejemplo

Vamos a suponer que estamos desarrollando un proyecto web, y lo estructuramos de manera más o menos acorde a los estándares actuales. Los directorios que tendríamos, por ejemplo, serían los siguientes:

1
| MiWebApp
|- /public
|  |- /css
|  |  |- style.css
|  |- /errors
|  |  |- 403.html
|  |  |- 404.html
|  |  |- 500.html
|  |- index.php
|- /src
|  |- /Model
|  |  |- UserModel.php
|  |- App.php
|- /tests
|  |- AppTest.php
  • /public: En esta carpeta guardamos todos los archivos que estarán en la raiz de un servidor web. Todo lo que queremos que sea accesible a “Internet”.
  • /src: Aquí guardamos la lógica correspondiente a la aplicación que estamos creando.
  • /tests: Si escribimos Tests Unitarios, entonces aquí es donde deberíamos guardarlos.

Hasta aquí, nada nuevo.

Pero si queremos agregar la posibilidad de utilizar el servidor web interno de PHP, entonces agregaremos unos directorios extras al proyecto (emulando los directorios de sistemas unix/linux):

  • /etc: Utilizado para almacenar archivos de configuración extras.
  • /bin: Archivos ejecutables desde línea de comandos. Usaremos este directorio para guardar nuestro router y un script.

Esta es la forma que tendrá nuestro proyecto con estos nuevos directorios:

1
| MiWebApp
|- /bin
|  |- init.sh
|  |- router.php
|- /etc
|  |- dev.ini
|- /public
|  |- /css
|  |  |- style.css
|  |- /errors
|  |  |- 403.html
|  |  |- 404.html
|  |  |- 500.html
|  |- index.php
|- /src
|  |- /Model
|  |  |- UserModel.php
|  |- App.php
|- /tests
|  |- AppTest.php

Archivo de configuración

Primero, creemos un archivo configuración inicial, al estilo de un php.ini, pero con seteos que solo serán válidos cuando estemos desarrollando. Por ejemplo, podemos activar todos los errores:

dev.ini
1
error_reporting = E_ALL

El Router

A continuación, dentro de la carpeta bin podremos alojar un archivo PHP con la lógica del router que vimos antes. Al utilizar un archivo de router con el servidor web, perdemos la capacidad por defecto de obtener información sobre cada petición en la consola, así que debemos incorporar esta funcionalidad. Creamos para ello, una función llamada logger, además de realizar algunos chequeos (lean los comentarios en el archivo):

router.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php
// Indicamos un Timezone
date_default_timezone_set("UTC");

// Indicamos el directorio donde se guardarán
// los archivos de error.
define("ERRORS", dirname(dirname(__FILE__)) . "/public/errors");

function logger($status = 200) {
file_put_contents(
"php://stdout",
sprintf("[%s] %s:%s [%s]: %sn\n",
date("D M j H:i:s Y"),
$_SERVER["REMOTE_ADDR"],
$_SERVER["REMOTE_PORT"],
$status,
$_SERVER["REQUEST_URI"]
)
);
}

// Si el archivo existe, devolvemos false y dejamos que el
// servidor web se haga cargo de la petición.
$path = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
if (file_exists($_SERVER["DOCUMENT_ROOT"] . $path)) {
return false;
}

// caso contrario, retornamos un archivo de
// error 404 que hemos creado nosotros.
logger(404);
http_response_code(404);
include ERRORS . "/404.html";
?>

No olvidemos entonces crear el archivo public/errors/404.html para que todo esto funcione

1
2
3
4
5
6
7
8
9
10
11
<!doctype html>  
<html lang="es">
<head>
<meta charset="utf-8">
<title>404</title>
</head>
<body>
<h1>404 No encontrado</h1>
<p>Lo lamento, pero el recurso solicitado no pudo ser encontrado.</p>
</body>
</html>

Script de lanzamiento

Ya solo nos queda crear un pequeño script para lanzar el servidor, que guardaremos dentro del directorio bin.

init.sh
1
2
3
4
5
6
7
8
9
10
#! /bin/bash

PHP=$(which php)
CONFIG="$(pwd)/etc/dev.ini"
ROOT="$(pwd)/public"
ROUTER="$(pwd)/bin/router.php"
HOST=0.0.0.0
PORT=8080

$PHP -S $HOST:$PORT -c $CONFIG -t $ROOT $ROUTER

Antes de continuar, deberíamos asegurarnos que este archivo tenga los permisos necesarios para ser ejecutado. Esto lo logramos, ubicados desde el directorio raíz, con el siguiente comando:

1
chmod +x ./bin/init.sh

Para lanzar el servidor ya solamente nos queda invocar el siguiente comando:

1
./bin/init.sh

Finalizando

Ciertamente, es muy sencillo comenzar a utilizar el servidor web de PHP para tener un ambiente rápido donde testear la aplicación, o bien, continuar desarrollándola. Este ejemplo en particular es muy básico, pero algunas mejoras que se pueden realizar son:

  • asegurarnos que el archivo router.php pueda chequear los accesos contra un listado permitido de direcciones IP. De no estar dentro de una lista, enviar un error tipo 403 Not allowed.

  • El error 404, podría dar algo más de información relacionada a la petición, para ello, en vez de un archivo 404.html podríamos incluir un archivo 404.php que tome las variables del array $_SERVER y arme una página más informativa.

Les dejo todo el código con el que estuvimos trabajando preparado para que lo descarguen y continúen realizando pruebas.

Hasta la próxima.