15 enero 2017

Subir imagenes al servidor usando un Web Services API REST

 
Interfaz visual del servidor de imagenes
Desde hace rato que me surgía esta duda y es que los API Rest actualmente son la manera de trabajar la web, ya que nos ofrecen infinitas herramientas y recursos para un trabajo grupal y de optimizacion, pero, en vista de que los web services son archivos textuales que nos sirve el servidor en formato JSON o XML, parece un poco complicado subir una imagen a través de un API ya que una imagen no es un texto, o si? Si no has leído mis entradas anteriores te invito a leerlas para que te empapes mas de lo que hablare en esta.


Luego de dar lectura a las dos entradas anteriores, y entender en que consiste la creación y consumo de un web services, podemos proseguir. En este caso, vamos a crear un servicio completo de subida de imágenes al servidor usando esta metodología de trabajo y entendiendo ciertas características del mismo. Primero, entendiendo que si vamos a subir una imagen al servidor haciendo uso de un REST API, es necesario que la imagen sea un texto, sino, como envías una imagen en un objeto de este estilo, y se preguntaran, en serio se puede hacer esto? codificar una imagen a texto? Y la verdad es que si, se puede, y de esta herramienta tan preciada haremos uso hoy.

 Antes de comenzar, cabe destacar que al final del post dejare un demo y un link para descargar los archivos. Tenemos 4 archivos esenciales que se basan en la siguiente estructura, cabe acotar que no son los únicos archivos incluidos dentro del directorio puesto a que tengo incluida también ciertas librerías de diseño y dinamismo, entre ellas angularJS y Materialize para CSS, pero, en el siguiente esquema, solo nos centraremos en los archivos que modificaremos, el resto de los archivos podrán encontrarlos en el enlace de descarga del proyecto completo


  • css
    • custom.css
  • js
    • custom.js
  • upload
  • index.html
  • server.php
css/custom.css

Este código no requiere mucha explicación, aunque haremos uso del framework de diseño Materialize, necesitaremos definir los siguientes estilos para ciertos elementos


body {
 background-color: #fefefe;
}

#imgCont {
 background:#eeeeee;
 width:auto;
 height:300px;
 padding: 5px 5px 5px 5px;
 border: 1px solid #cccccc;
 margin-top:10px;
 overflow: auto;
}

#imgCont img {
 width:90px;
 border:1px solid #bbbbbb;
 margin-right:5px;
 transition:all ease .2s;
 -webkit-transition:all ease .2s;
}
#imgCont img:hover {
 opacity:.8;
 transition:all ease .2s;
 -webkit-transition:all ease .2s;
}
css/custom.js

Aquí empieza la función, si bien no podemos enviar una imagen como tal a un servicio web, si lo podemos hacer si codificamos la imagen, en este caso, haremos uso de base64, la cual es una codificación que tiene la capacidad de convertir cualquier dato definido a nivel de bytes en un formato seguro de transportar por Internet como lo son los caracteres ASCII, es decir, no solo sirve para codificar imágenes, sirve para codificar cualquier archivo, música, vídeos, documentos, o simplemente, cualquier cadena de texto o carácter que se represente en bytes, es decir, puede transformar todo lo que sea recurso digital. En nuestro controlador de imagen en angular simplemente definimos la función encodeImageFileAsURL(); la cual se encarga de hacer todo el trabajo de codificación, y luego la cadena es enviada al web services a través de un objeto.


     var app = angular.module('crud',[]);


  // Controlador principal de nuestra pagina
  app.controller('mainCtrl', function($scope,$http) {
   $scope.dominio = "http://localhost/imagenes/";
         $scope.message = 'Ejemplo';
         $scope.subtitle = "Subir una imagen haciendo uso de API REST";

         $scope.imageJSON = {};

         $scope.enlaces = {};

         angular.element(document).ready(function () {
       $scope.get();  
      });

         // La funcion post() que hace la solicitud para publicar un nuevo elemento
      $scope.post = function() {
       $http.post("server.php", $scope.imageJSON)
        .then(function (response){
               console.log(response);
               Materialize.toast(response.data.statusMessage, 4000);


               if(response.data.data != null) {
                $scope.cargarImagen(response.data.data);
               } 

               $scope.get();
           }, 
           function(response) {
            // Aqui va el codigo en caso de error
           });
      }

      $scope.get = function() {
       $http.get("server.php")
        .then(function (response){
               console.log(response);
               $scope.imagenes = response.data.data;

               
           }, 
           function(response) {
            // Aqui va el codigo en caso de error
           });
      }

      $scope.cargarImagen = function(ruta) {


       var container = document.getElementById('imgTest');
       var newImage = document.getElementById('imagen');
          newImage.src = ruta;
          container.style.width = 'auto';
          container.style.height = 'auto'

          container.innerHTML = newImage.outerHTML;

         $scope.enlaces.link = $scope.dominio + ruta;
         $scope.enlaces.html = "<a href='"+ $scope.dominio + ruta +"'>"+$scope.dominio + ruta+"</a>";
         $scope.enlaces.foros = "[IMG]"+$scope.dominio + ruta+"[/IMG]"
      }


         $scope.encodeImageFileAsURL = function() {

       var filesSelected = document.getElementById("inputFileToLoad").files;

       if (filesSelected.length > 0) {
         var fileToLoad = filesSelected[0];

         var fileReader = new FileReader();

         fileReader.onload = function(fileLoadedEvent) {
          
           var srcData = fileLoadedEvent.target.result; // <--- data: base64
      $scope.imageJSON.encodedImage = srcData;


      $scope.post();
         }
         fileReader.readAsDataURL(fileToLoad);
       }
     }



     });
server.php

Este archivo trabaja del lado del servidor, en este caso apache, el archivo recibe el texto de la imagen codificada, y luego la pasa a la función save_base64_image();, la cual, no hace mas que analizar la cadena enviada y mediante el uso de herramientas de decodificacion y escritura en el servidor, crea la imagen en el directorio upload del mismo, almacenando asi una imagen con una extensión, y creando la URL especifica del mismo.


<?php 
 header("Access-Control-Allow-Origin: *");
 // Permite la ejecucion de los metodos
 header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE"); 
 
 switch ($_SERVER['REQUEST_METHOD']) {

  case 'GET':
   $directorio = opendir("./upload"); //ruta actual

   $contador = 0;

   while ($archivo = readdir($directorio)) //obtenemos un archivo y luego otro sucesivamente
   {
       if (!is_dir($archivo))//verificamos si es o no un directorio
       {
           $listado[$contador] = $archivo;
       }
     $contador++;
   }


   

   $listado = array_values($listado);

   if(count($listado)!=0) {
    print_json(200, true, $listado);
   } else {
    print_json(200, "No existen elementos", null);
   }

   break;
  case 'POST':
    $content = file_get_contents("php://input");

    $array = json_decode($content, true);


    $validar = validate($array['encodedImage']);

    if($validar==1) {
     $imagen = save_base64_image($array['encodedImage'], "upload/IMG_" . strtoupper(md5($array['encodedImage'])));

     if($imagen!=null) {
      print_json(200, "Completado", $imagen);

     } else {
      print_json(200, "Este archivo ya existe", null);
     }
    } else {
     print_json(200, "Extension invalida", null);
    }

    
    
   break;
  default:
   print_json(405, 'Metodo no permitido', null);
   break;
 }

 function validate($string_base64) {
  $array = explode(",", $string_base64);
  if( ($array[0] == "data:image/jpeg;base64") || ($array[0] == "data:image/gif;base64") || ($array[0] == "data:image/png;base64") ) {
   return 1;
  } else {
   return 0;
  }
 }
 



 function save_base64_image($base64_image_string, $output_file_without_extentnion, $path_with_end_slash="" ) {
     //usage:  if( substr( $img_src, 0, 5 ) === "data:" ) {  $filename=save_base64_image($base64_image_string, $output_file_without_extentnion, getcwd() . "/application/assets/pins/$user_id/"); }      
     //
     //data is like:    data:image/png;base64,asdfasdfasdf
     $splited = explode(',', substr( $base64_image_string , 5 ) , 2);
     $mime=$splited[0];
     $data=$splited[1];

     $mime_split_without_base64=explode(';', $mime,2);
     $mime_split=explode('/', $mime_split_without_base64[0],2);
     if(count($mime_split)==2)
     {
         $extension=$mime_split[1];
         if($extension=='jpeg')$extension='jpg';
         //if($extension=='javascript')$extension='js';
         //if($extension=='text')$extension='txt';
         $output_file_with_extentnion.=$output_file_without_extentnion.'.'.$extension;
     }

     if(file_exists($output_file_with_extentnion)) {
      return null;
     } else {
      file_put_contents( $path_with_end_slash . $output_file_with_extentnion, base64_decode($data) );
      return $output_file_with_extentnion;
     }
     
     
 }

 function print_json($status, $mensaje, $data) {
  header("HTTP/1.1 $status $mensaje");
  header("Content-Type: application/json; charset=UTF-8");

  $response['statusCode'] = $status;
  $response['statusMessage'] = $mensaje;
  $response['data'] = $data;

  echo json_encode($response, JSON_PRETTY_PRINT);
 }


?>
index.html
Esta sera la pagina principal, donde se encuentra toda la interaccion con el usuario, y donde se embeden todos los archivos necesarios para el funcionamiento.


<!DOCTYPE html>
<html lang="es" ng-app="crud" >
<head>
 <meta charset="UTF-8">
 <title>Rest API Visual Client</title>
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <!-- Incluimos el CND de Materialize CSS -->
 <link rel="stylesheet" href="css/materialize.min.css">
 <!-- Nuestro CSS personalizado -->
 <link rel="stylesheet" href="css/custom.css">
 <!-- Incluimos el CND de AngularJS -->
 <script src="js/angular.min.js"></script>
 <!-- Incluimos el CND de la libreria jQuery -->
 <script src="js/jquery-3.1.1.min.js"></script>
 
</head>
<body class="container" ng-controller="mainCtrl">

 <div class="row">
  <div class="col s12">
   <h1>{{message}}</h1>
   <p>{{subtitle}}</p> 
  </div>
 </div>

 <div class="row">
  <div class="col s12 m6 l6">
   <div class="row">
    <div class="col s12">
     <h6><small>Solo JPG, PNG, GIF</small></h6>
     <input id="inputFileToLoad" type="file"/>
     <button class="btn" ng-click="encodeImageFileAsURL()">Subir</button>
     
     
    </div>
    <div class="col s12">
     <div id="imgCont">
      <img ng-click="cargarImagen('upload/' + x)" ng-repeat="x in imagenes" src="upload/{{x}}" title="{{x}}" alt="{{x}}">
     </div>


    </div>

    <div class="input-field col s12">
        <input id="Link" type="text" placeholder="Enlace directo" class="validate" readonly ng-value="enlaces.link" ng-model="enlaces.link">
    </div>
    <div class="input-field col s12">
        <input id="HTML" type="text" class="validate" placeholder="Codigo HTML" readonly ng-value="enlaces.html" ng-model="enlaces.html">
    </div>
    <div class="input-field col s12">
        <input id="Forum" type="text" class="validate" placeholder="Codigo para foros" readonly ng-value="enlaces.foros" ng-model="enlaces.foros">
    </div>

   </div>
   
  </div>

  <div class="col s12 m6 l6">
   <div id="imgTest" class="center-align">

    <div class="preloader-wrapper small active">
        <div class="spinner-layer spinner-green-only">
          <div class="circle-clipper left">
            <div class="circle"></div>
          </div><div class="gap-patch">
            <div class="circle"></div>
          </div><div class="circle-clipper right">
            <div class="circle"></div>
          </div>
        </div>


    </div>
    <p>Esperando imagen</p>

    <img id="imagen" width="100%" alt="">

   </div>
  </div>
 </div>



 
     
 <!-- Nuestro codigo JavaScript personal -->
 <script src="js/custom.js"></script> 
 <!-- Incluimos el CND de Materialize JS -->
   <script src="js/materialize.min.js"></script>
</body>
</html>

Esto es todo, para entender mejor, les voy a dejar una demo y el link de descarga en Github para que lo tengan en su computador

  Demo Descargar

Enlaces para compartir en tu blog o pagina web.




Widget por Friki Bloggeo