Contenido


Crear con servicios de IBM Cloud una aplicación basada en el navegador para almacenar y buscar PDFs, Parte 2

Utilizar el servicio Object Storage para almacenar sus archivos

Comments

Contenido de la serie:

Este contenido es la parte # de # de la serie: Crear con servicios de IBM Cloud una aplicación basada en el navegador para almacenar y buscar PDFs, Parte 2

Manténgase en contacto por contenidos adicionales de esta serie.

Este contenido es parte de la serie:Crear con servicios de IBM Cloud una aplicación basada en el navegador para almacenar y buscar PDFs, Parte 2

Manténgase en contacto por contenidos adicionales de esta serie.

En la Parte 1 de esta serie de artículos, le guié a través de la utilización de dos servicios importantes: Document Conversion y Keyword Extraction. También le mostré cómo utilizar estos servicios en una aplicación del mundo real que permite que los usuarios almacenen e indexen documentos PDF de forma inteligente para realizar búsquedas más eficientes.

En esta parte final, le presento el servicio IBM® Cloud™Object Storage que brinda una infraestructura de almacenamiento de objetos fiable y que cumple con los estándares. Le muestro cómo utilizar el servicio con PHP para crear una residencia feliz para sus subidas de PDFs.

En este segmento final, se mejora la solución de almacenamiento de documentos añadiendo la búsqueda de palabras clave y la recuperación de archivos PDF, después se despliega a IBM Cloud.

También le muestro cómo construir un motor de búsqueda que aprovecha los índices de texto de MongoDBsus para habilitar la búsqueda de palabras clave en su nuevo almacén de documentos, y luego le guío a través del proceso de desplegarlo a IBM Cloud. Siga leyendo, ¡tengo mucho reservado!

Ejecutar la aplicaciónObtener el código en GitHub

Entender y configurar el servicio Object Storage

El servicio Object Storage permite que sea fácil almacenar y recuperar datos no estructurados de la nube de IBM Cloud. Es compatible con la API de OpenStack Swift, y sigue la jerarquía de tres niveles para organizar los datos de Swift: cuentas, contenedores y objetos. Funciona así:

  • La principal unidad de la jerarquía es una cuenta. Las cuentas corresponden a usuarios; para acceder a una cuenta, el usuario debe proporcionar las credenciales de autenticación.
  • Una cuenta puede alojar múltiples contenedores que, en líneas generales, serían los equivalentes a carpetas o subdirectorios de un sistema de archivos tradicional.
  • Cada contenedor puede almacenar múltiples objetos, que pueden ser archivos o datos. Los objetos pueden tener metadatos adicionales definidos por el usuario. Normalmente, es posible almacenar un número ilimitado de objetos.

Para ver esto funcionamiento, inicie en IBM Cloud una nueva instancia del servicio Object Storage iniciando sesión en su cuenta de IBM Cloud. Busque y seleccione el servicio Object Storage .

Revise la descripción del servicio y haga clic para lanzarlo. Asegúrese de que el campo "Conectar a" se ha establecido a "Dejar independiente" y que está utilizando el "Plan Gratuito". Inicialmente, la instancia de este servicio se ejecuta en estado independiente. Similar a la instancia del servicio Document Conversión de la Parte 1, este estado independiente permite que la aplicación sea desarrollada localmente con la propia instancia del servicio alojada remotamente en IBM Cloud.

Figura 1. Inicio del servicio Object Storage
Object Storage service initialization
Object Storage service initialization

Una vez hecho, se inicia la instancia del servicio y se le presenta una página de información del servicio. Despliegue la barra de navegación de la izquierda y haga clic en el enlace "Credenciales del Servicio" para ver el URL, la región, el nombre de usuario, la contraseña y las otras credenciales de la instancia del servicio. Anote todas esas credenciales, ya que las necesitará en los siguientes pasos.

Figura 2. Credenciales del servicio Object Storage
Object storage service credentials
Object storage service credentials

Ahora puede probar el servicio Object Storage, mediante la utilización de un cliente de REST como Postman para enviarle algunas solicitudes de muestra. Como de costumbre, comience enviando una solicitud POST con el nombre de usuario y la contraseña del servicio al URL de autenticación del mismo. Si la autenticación tiene éxito, el servidor devuelve un código de respuesta 200 OK y una cabecera X-Subject-Token que contiene un token de autenticación que se debe utilizar en las solicitudes posteriores.

Por ejemplo, si el URL de autenticación del servicio es https://identity.open.softlayer.com/v3/auth/tokens, envíe una solicitud POST que contenga el nombre de usuario y la contraseña del servicio en el cuerpo del contenido de JSON. Si es autenticada, la respuesta del servidor incluye una cabecera X-Subject-Token que contiene un token de autenticación. Aquí presento un ejemplo de solicitud y respuesta:

Figura 3. Solicitud/respuesta de muestra para el servicio de autenticación de Object Storage
Sample request/response for object storage service authentication
Sample request/response for object storage service authentication

La respuesta también incluye una serie de extremos, uno para cada servicio disponible en este despliegue de OpenStack. Revíselo hasta que encuentre el extremo del servicio Object Storage, como se muestra abajo. El URL de este extremo será el objetivo de todas las solicitudes posteriores.

Figura 4. Extremo del servicio Object Storage
Object storage service endpoint
Object storage service endpoint

Una vez que tiene el token de autenticación y el URL del extremo, puede comenzar a interactuar con el servicio Object Storage utilizando la API de Swift. Por ejemplo, para añadir un contenedor nuevo, envíe una solicitud PUT al URL del extremo, añadiendo el nombre del contenedor nuevo al final del URL y acuérdese de incluir en la solicitud una cabecera X-Auth-Token con el token de autenticación. Así que, para crear un contenedor nuevo llamado "container1", si URL del extremo es https://xyz.objectstorage.softlayer.net/AUTH_123, tiene que enviar una solicitud PUT a https://xyz.objectstorage.softlayer.net/AUTH_123/container1. Aquí tiene un ejemplo:

Figura 5. Solicitud/respuesta de muestra para la creación de un contenedor de almacenamiento de objetos
Sample request/response for object storage container creation
Sample request/response for object storage container creation

De forma similar, si quiere hacer una lista con todos los contenedores de la cuenta, puede enviar una solicitud GET sin ningún parámetro al URL del extremo correspondiente – en este ejemplo, https://xyz.objectstorage.softlayer.net/AUTH_123 - como se muestra abajo:

Figura 6. Solicitud/respuesta de muestra para crear la lista de contenedores del almacenamiento de objetos
Sample request/response for object storage container listing
Sample request/response for object storage container listing

Almacenar los archivos subidos en la instancia del servicio Object Storage

Aunque realmente es posible interactuar con la API de Object Storage utilizando solicitudes y respuestas como las mostradas anteriormente, para los desarrolladores de aplicaciones es más fácil utilizar la solución php-opencloud, un SDK de PHP para despliegues basados en OpenStack. Este SDK brinda un envoltorio PHP práctico que envuelve los métodos de la API de Swift, para que simplemente necesite llamar al método adecuado, por ejemplo, createContainer() o listContainers(). La biblioteca del cliente se ocupará de formular las solicitudes y decodificar la respuesta para usted. Aún así, todavía tiene que saber qué es lo que ocurre en el código, para depurar errores y, por si quiere, realizar una operación que el SDK no soporta en la actualidad.

Se incluyó el paquete php-opencloud en el archivo de dependencia del compositor mostrado en la Parte 1 de este artículo, así que ya debería tenerlo instalado en sus sistema de desarrollo. Antes de usarlo, copie las credenciales de su servicio Object Storage a la aplicación PHP. Edite el archivo $APP_ROOT/config.php y añádale las credenciales siguiendo el ejemplo de abajo:

<?php
$config['settings']['object-storage']['url'] = "URL";
$config['settings']['object-storage']['region'] = "REGION";
$config['settings']['object-storage']['user'] = "USERNAME";
$config['settings']['object-storage']['pass'] = "PASSWORD";

Después, añada el código abajo al archivo $APP_ROOT/public/index.php, para utilizar esta configuración para iniciar un cliente OpenStack nuevo que usa el contenedor DI de Slim:

<?php
// Inicio de la aplicación Slim - recortado

// inicia el contenedor de inyección de dependencias
$container = $app->getContainer();

// añade el cliente del servicio Object Storage
$container['objectstorage'] = function ($container) {
  $config = $container->get('settings');
  $openstack = new OpenStack\OpenStack(array(
    'authUrl' => $config['object-storage']['url'],
    'region'  => $config['object-storage']['region'],
    'user'    => array(
      'id'       => $config['object-storage']['user'],
      'password' => $config['object-storage']['pass']
  )));
  return $openstack->objectStoreV1();
};

Finalmente, actualice en el mismo archivo el controlador de solicitudes /add POST, para utilizar este cliente y guardar el archivo PDF subido en la instancia de Object Storage:

<?php
// Inicio de la aplicación Slim - recortado

// carga el procesador
$app->post('/add', function (Request $request, Response $response) {

  // obtiene la configuración
  $config = $this->get('settings');


  try {
    // Verifica para encontrar una carga de archivo válida
    if (empty($_FILES['upload']['name'])) {
      throw new Exception('No se ha cargado ningún archivo');
    }

    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $type = $finfo->file($_FILES['upload']['tmp_name']);
    if ($type != 'application/pdf') {
      throw new Exception('El formato del archivo no es válido');
    }

    // convierte a texto el PDF subido
    // conecta con la API de conversión de documentos de Watson
    // transfiere el archivo subido para su conversión a formato de texto
    $apiResponse = $this->converter->post(
      'v1/convert_document?version=2015-12-15', array('multipart' => array(
        array('name' => 'config',
          'contents' => '{"conversion_target":"normalized_text"}'),
        array('name' => 'file',
          'contents' => fopen($_FILES['upload']['tmp_name'], 'r'))
    )));
    
    // almacena la respuesta
    $text = (string)$apiResponse->getBody();
    unset($apiResponse);

    // extrae las palabras clave del texto
    // conecta con Watson/Alchemy API para la extracción de las palabras clave
    // transfiere el contenido del texto para la extracción de las palabras clave
    // solicitará la salida de JSON
    $apiResponse = $this->extractor->post(
      'text/TextGetRankedKeywords', array('form_params' => array(
        'apikey' => $config['alchemy']['apikey'],
        'text' => strip_tags($text),
        'outputMode' => 'json'
    )));

    // procesa la respuesta
    // crea el arreglo de las palabras clave
    $body = $apiResponse->getBody();
    $data = json_decode($body);
    $keywords = array();
    foreach ($data->keywords as $k) {
      $keywords[] = $k->text;
    }

    // guarda las palabras clave en MongoDB
    $collection = $this->db->docs;
    $q = trim($_FILES['upload']['name']);
    $params = $request->getParams();
    $result = $collection->findOne(array('name' => $q));
    $doc = new stdClass;
    if (count($result) > 0) {
      $doc->_id = $result['_id'];
    }
    $doc->name = trim($_FILES['upload']['name']);
    $doc->keywords = $keywords;
    $doc->description = trim(strip_tags($params['description']));
    $doc->updated = time();
    $collection->save($doc);

    // guarda el PDF en el almacenamiento de objetos
    $service = $this->objectstorage;
    $containers = $service->listContainers();

    $containerExists = false;
    foreach($containers as $c) {
      if ($c->name == 'documents') {
        $containerExists = true;
        break;
      }
    }
    
    if ($containerExists == false) {
      $container = $service->createContainer(array(
        'name' => 'documents'
      )); 
    } else {    
      $container = $service->getContainer('documents');
    }
      
    $stream = new Stream(fopen($_FILES['upload']['tmp_name'], 'r'));
    $options = array(
      'name'   => trim($_FILES['upload']['name']),
      'stream' => $stream,
    );
    $container->createObject($options);

    $response = $this->view->render($response, 'add.phtml',
      array('keywords' => $keywords,
        'object' => trim($_FILES['upload']['name']),
        'router' => $this->router));
    return $response;

  } catch (ClientException $e) {
    // en el caso de excepción de Guzzle
    // muestra el contenido de la respuesta HTTP
    throw new Exception($e->getResponse());
  }

});

Ya ha visto algo de este código en la Parte 1: verificar el archivo subido, convertirlo a texto normalizado, extraer sus palabras clave y guardarlas en una base de datos MongoDB. El código adicional comienza mediante el uso del cliente de Object Storage para hacer una lista de los contenedores disponibles utilizando listContainers() . Después revisa la lista de contenedores para verificar si existe un contenedor llamado documents , si no existe, invoca el método createContainer() para crear un contenedor nuevo con ese nombre. O, si el contenedor ya existe, utiliza el método getContainer() para obtener una referencia del contenedor.

Una vez se ha obtenido la referencia del contenedor documents , el paso siguiente es iniciar una secuencia desde el documento PDF que se ha subido. Esta secuencia se pasa al método createObject() del contenedor como parte de un arreglo de opciones, que incluye el nombre deseado para el objeto del contenedor. El métodocreateObject() se ocupa de transferir y guardar el documento como un objeto con nombre en la instancia de Object Storage. Como verá en el Paso 4, en cualquier momento el nombre del objeto se puede usar como una clave para recuperar el documento PDF.

Construir una interfaz de búsqueda

Una vez que se han guardado los documentos y palabras clave, lo único que falta por hacer es construir una interfaz de búsqueda que permite analizar rápidamente la lista de palabras y encontrar documentos que coincidan.

Bueno, recuerde que las palabras clave extraídas de cada PDF fueron salvadas como un arreglo de cadena de caracteres en la propiedad keywords del documento MongoDB correspondiente. Para que sea más fácil buscar en este arreglo, añada un índice de texto a la propiedad keywords , utilizando un comando como el de abajo:

db.docs.createIndex({ keywords: "text" })

Aquí tiene un ejemplo de cómo hacer esto utilizando la interfaz de MongoLab:

Figura 7. Crear índices de texto en colección de MongoDB
Text index creation on MongoDB collection
Text index creation on MongoDB collection

Después, añada a su aplicación Slim una ruta /search y un controlador de devoluciones de llamada, en el archivo $APP_ROOT/public/index.php, como se muestra abajo:

<?php
// Inicio de la aplicación Slim - recortado

$app->get('/search', function (Request $request, Response $response) {
  $params = $request->getQueryParams();
  $results = array();
  if (isset($params['q'])) {
    $q = trim(strip_tags($params['q']));
    if (!empty($q)) {
      $where = array(
        '$text' => array('$search' => $q) 
      );  
      $collection = $this->db->docs;
      $results = $collection->find($where)->sort(array('updated' => -1));    
    }
  }
  $response = $this->view->render($response, 'search.phtml', 
    array('router' => $this->router, 'results' => $results));
  return $response;
})->setName('search');

Esta devolución de llamadas maneja las solicitudes para el extremo de URL /search y revisa en busca de una cadena de caracteres de consulta en el URL de la solicitud. Si se encuentra una cadena de caracteres de consulta, inicia el cliente de MongoDB client y utiliza el método find() del cliente para generar y ejecutar una consulta de búsqueda de MongoDB en el índice de texto. Los resultados se ordenan poniendo primeros los documentos subidos más recientemente. El métodofind() devuelve un cursor a esta colección de resultados, y se pasa este cursor al script de vista para mostrarlo.

El script de vista se parece a esto:

<div class="panel panel-default">
  <form method="get" 
    action="<?php echo $data['router']->pathFor('search'); ?>">
    <div class="input-group">
      <input type="text" name="q" class="form-control" 
        placeholder="Search for...">
      <span class="input-group-btn">
        <button type="submit" class="btn btn-default">Search</button>
      </span>
    </div>  
  </form>
</div>  

<?php if (isset($data['results']) && count($data['results'])): ?>
<h4>Search Results</h4>
<ul class="list-group row clearfix">
<?php foreach ($data['results'] as $doc): ?>
  <li class="list-group-item clearfix" style="border:none">
  <strong><?php echo $doc['name']; ?></strong>
    <a href="<?php echo $data['router']->pathFor('download',
      array('id' => $doc['name'])); ?>"
      class="btn-sm btn-success">Download</a> <br />
  <?php echo $doc['description']; ?> <br />
  Last updated: <?php echo date('d M Y', $doc['updated']); ?>
  <br />
  </li>
<?php endforeach; ?>
</ul>
<p>Esta operación utilizó los datos generados a través de los servicios
  <a href="http://www.ibm.com/smarterplanet/us/en/ibmwatson/">IBM Watson</a>
  y <a href="https://www.alchemyapi.com/">AlchemyAPI</a>.</p>
<?php endif; ?>
</div>

Este script de vista tiene dos componentes principales:

  • Un formulario de búsqueda que contiene un campo de ingreso de texto para que el usuario introduzca una o varias palabras clave. Al dar a enviar, se envían los datos introducidos por el usuario al extremo del URL /search como una solicitud GET.
  • Un panel de resultado de búsqueda itera en la colección de documentos de MongoDB que fue devuelta por el controlador /search y, muestra el nombre del documento, la descripción y la última fecha de actualización para cada resultado encontrado. Todas las entradas contienen además un enlace al extremo del URL /download que habilita que el usuario se descargue el documento PDF correspondiente del servicio Object Storage.

Aquí hay un ejemplo de cómo es:

Figura 8. Formulario de búsqueda y resultados
Search form and results
Search form and results

Recuperación de documentos del almacén

El script de vista mostrado en la sección anterior incluye enlaces al extremo del URL /download, que se supone que habilita que los usuarios se descarguen un documento PDF del servicio Object Storage. También observará que el URL /download incluye el nombre del documento PDF como parte del URL.

Internamente, el controlador de las devoluciones de llamada para el extremo /download debe iniciar un cliente de Object Storage nuevo y luego utilizar los métodos del cliente para descargar el objeto binario correspondiente, utilizando su nombre como una clave. Después este objeto debe ser enviado como una secuencia al navegador del usuario, para que pueda ser guardado localmente.

Este es el código para el controlador /download que realiza todas las tareas anteriores:

<?php
// Inicio de la aplicación Slim - recortado

$app->get('/download/{id}', function (Request $request, Response $response, $args) {
  $service = $this->objectstorage;
  $stream = $service->getContainer('documents')
                  ->getObject(trim(strip_tags($args['id'])))
                  ->download();
  $response = $response->withHeader('Content-Type', 'application/pdf')
                       ->withHeader('Content-Disposition', 'attachment; filename="' . trim(strip_tags($args['id'])) .'"')
                       ->withHeader('Content-Length', $stream->getSize())
                       ->withHeader('Expires', '@0')
                       ->withHeader('Cache-Control', 'must-revalidate')
                       ->withHeader('Pragma', 'public');
  $response = $response->withBody($stream);
  return $response;
})->setName('download');

El controlador anterior inicia un cliente de Object Storage nuevo y luego utiliza los métodos getContainer() y getObject() del cliente para obtener una referencia del contenedor documents y el objeto especificado en el URL de la solicitud. Después utiliza el método download() del cliente para crear una secuencia que contiene el archivo PDF.

Se modifica el objeto de Slim Response para incluir varias cabeceras, incluyendo las de Content-Type, Content-Dispositiony Content-Length , que dirán al navegador que lo que sigue es un objeto binario. Después, con el método withBody() se añade la secuencia al objeto Response y se envía al cliente que la está solicitando.

Desplegar a IBM Cloud

En este punto, la aplicación está completa y puede ser desplegada a IBM Cloud. Para hacerlo, primero cree el archivo de manifiesto de la aplicación en $APP_ROOT/manifest.yml, recuerde utilizar un alojamiento y un nombre de aplicación únicos añadiéndoles una cadena de caracteres aleatorios (como sus iniciales).

---
aplicaciones:
- nombre: pdf-keyword-search-[iniciales]
memoria: 256M
instancias: 1
alojamiento: pdf-keyword-search-[iniciales]
buildpack: https://github.com/cloudfoundry/php-buildpack.git
pila: cflinuxfs2

El paquete de compilación de Cloud Foundry PHP no incluye de forma predeterminada la extensión PHP MongoDB ni la extensión fileinfo (utilizada para validar la subida de archivos PDF), así que debe configurar el paquete de compilación para habilitar estas extensiones durante el despliegue. De forma similar, debe configurar el paquete de compilación para utlizar el directorio público de la aplicación como directorio del servidor web. Cree un archivo $APP_ROOT/.bp-config/options.json con el siguiente contenido:

{
    "WEB_SERVER": "httpd",
    "PHP_EXTENSIONS": ["bz2", "zlib", "curl", "mcrypt", "mongo", "fileinfo"],
    "WEBDIR": "public",
    "PHP_VERSION": "{PHP_56_LATEST}"
}

También, si quiere que las credenciales del servicio para los servicios Document Conversion y Object Storage sean obtenidas automáticamente de IBM Cloud, actualice el script $APP_ROOT/public/index.php para utilizar la variable VCAP_SERVICES IBM Cloud, como se muestra a continuación:

<?php
// incluyo el autocargador y la configuración
require '../vendor/autoload.php';
require '../config.php';

// si el entorno IBM Cloud VCAP_SERVICES está disponible
// sobrescribe con las credenciales de IBM Cloud
if ($services = getenv("VCAP_SERVICES")) {
  $services_json = json_decode($services, true);
  
  $config['settings']['document-conversion']['user'] = 
    $services_json["document_conversion"][0]["credentials"]["username"];
  $config['settings']['document-conversion']['pass'] = 
    $services_json["document_conversion"][0]["credentials"]["password"];

  $config['settings']['object-storage']['url'] = 
    $services_json["Object-Storage"][0]["credentials"]["auth_url"];
  $config['settings']['object-storage']['region'] = 
    $services_json["Object-Storage"][0]["credentials"]["region"];;
  $config['settings']['object-storage']['user'] = 
    $services_json["Object-Storage"][0]["credentials"]["userId"];;
  $config['settings']['object-storage']['pass'] = 
    $services_json["Object-Storage"][0]["credentials"]["password"];;
} 

// inicia la aplicación
$app = new \Slim\App($config);

// ruta de las devoluciones de las llamadas - recortada

Puede continuar e inserte la aplicación en IBM Cloud, luego enlácela los servicios Document Conversion y Object Storage que inició en la Parte 1. Recuerde utilizar el ID correcto en todas las instancias del servicio, para garantizar que están correctamente enlazadas a la aplicación.

shell> cf api https://api.ng.bluemix.net
shell> cf login
shell> cf push
shell> cf bind-service pdf-keyword-search-[iniciales] "Document Conversion-[id]"
shell> cf bind-service pdf-keyword-search-[iniciales] "Object Storage-[id]"
shell> cf restage pdf-keyword-search-[iniciales]

Es posible empezar a utilizar la aplicación navegando al alojamiento especificado en el manifiesto de la aplicación, por ejemplo, http://pdf-keyword-search-[iniciales].mybluemix.net. Si ve una página vacía u otro tipo de error, utilice el enlace de la parte superior de esta sección para depurar su código PHP y descubrir en qué parte están yendo mal las cosas.

Conclusión

Este artículo está enfocado a coordinar varios servicios de Watson y IBM Cloud para solucionar un problema común: filtrando rápidamente una colección de documentos grande para encontrar solo aquellos que coincidan con ciertas palabras clave. Mediante la combinación de computación cognitiva con infraestructura y almacenamiento en nube confiable y escalable, unidos por un poco de PHP, se demuestra lo fácil que es para los desarrolladores construir poderosas soluciones para el almacenamiento y búsqueda de documentos en la nube.

Si quiere experimentar los servicios discutidos en este artículo, empiece probando la demo de la aplicación en vivo. Recuerde que esta es una demo pública, así que tenga cuidado para no subir información confidencial o delicada (también hay un útil botón "Restablecer Sistema" que puede utilizar para borrar todos los datos subidos). Después, descargue el código del repositorio de GitHub y vea más de cerca cómo encaja todo.

Por supuesto, este artículo está sólo enfocado en un caso de uso en particular, mediante la utilización de otros servicios de IBM Cloud y Watson, puede mezclar cosas para crear otras aplicaciones cognitivas para manejar documentos. Para saber más, consulte los enlaces de la parte superior de esta sección, y conocerá sobre el servicio IBM Cloud Document Conversion, la API de AlchemyAPI Keyword Extraction, el servicio IBM Cloud Object Storage, la microinfraestructura Slim y otras herramientas y técnicas utilizadas en este artículo. ¡Feliz programación!


Recursos para Descargar


Temas relacionados


Comentarios

Inicie Sesión o Regístrese para agregar comentarios.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=90
Zone=Cognitive computing
ArticleID=1035950
ArticleTitle=Crear con servicios de IBM Cloud una aplicación basada en el navegador para almacenar y buscar PDFs, Parte 2: Utilizar el servicio Object Storage para almacenar sus archivos
publish-date=08152016