mercoledì 27 novembre 2013

Un semplice sistema API GET CodeIgniter con risposta JSON

In questo post volevo descrivere una implementazione relativa ad un sistema API che usa esclusivamente il metodo HTTP GET, sempre in ottica di progetti web strutturati con una netta separazione tra vista (lato client) e controllo (lato server).
Il mio framework php preferito (CodeIgniter) gestisce in modo nativo le richieste con metodo HTTP GET, quindi creare un semplice sistema di API a sola lettura è molto semplice. Ipotizziamo di creare una sottocartella chiamata api nella cartella controller, e di posizionarvi dentro i vari controller che rispecchieranno le risorse delle API.

application
   |- controllers
      |- api
         |- news.php

A questo punto sappiamo che CodeIgniter gestirà che richieste del tipo
http://www.example.com/api/news/getAll
http://www.example.com/api/news/getSingle/12
richiedendo l'esecuzione del controller news.php che compare nell'albero di esempio, con i metodi getAll senza parametri e getSingle con un parametro GET valorizzato a "12".
Vediamo quindi il codice del controller:
class News extends CI_Controller {

 public function getAll()
 {
  $query = $this->db->get('news');
  $elenco_news = array();
  foreach ($query->result() as $row)
  {
       $news = new stdClass();
       $news->id = $row->id;
       $news->titolo = $row->titolo;
       $news->contenuto = $row->contenuto;
       array_push($elenco_news, $news);
  }
  echo json_encode($elenco_news);
 }
 
 public function getSingle(id)
 {
  $this->db->from('news')->where('id',$id);
  $query = $this->db->get();
  $row = $query->row(); 
  $news = new stdClass();
  $news->id = $row->id;
  $news->titolo = $row->titolo;
  $news->contenuto = $row->contenuto;
  echo json_encode($news);
 }

}
Sarà sufficiente quindi creare un controller per ogni tipo di risorsa (ad esempio news, eventi, banner) ed i relativi metodi, ricordandosi di strutturare le risposte in formato JSON come da esempio.

martedì 19 novembre 2013

Chiamate AJAX cross domain JQuery ad API JSON

Ultimamente i progetti web-based sono strutturati tutti in modo simile, ossia con un backend API e un frontend che interroga le API, solitamente in ajax.
In particolare i miei progetti sono caratterizzati da PHP + CodeIgniter lato API (con risposte JSON), e jQuery lato frontend.
Il jQuery si comporta magnificamente con le chiamate Ajax, ma qualche giorno fa mi è capitato il caso di interrogare le API da un dominio diverso da quello del server dove risiedono. Il problema è relativo al fatto che jQuery non permette chiamate ajax cross-domain con tipologia JSON oppure testo normale, come restrizione di sicurezza.
Leggendo la documentazione, viene specificato che le chiamate cross-domain sono possibili solo per la tipologia di scambio dati JSONP.
Ma che diavolo è JSONP? Il fantomatico JSON with Padding è un formato di interscambio dati basato su due semplici punti:
- una funzione di callback
- come parametro della precedente, il JSON che intendiamo trasmettere.
Ad esempio, questo è JSONP
funzionecallback({"status":"ok"})
Mentre il rispettivo JSON sarebbe
{"status":"ok"}
La funzione di callback lato client si può specificare mediante un apposito comando di configurazione di $.ajax oppure lasciare che sia jQuery a decidere il nome della callback ed a inviarlo come parametro GET (chiamato appunto, callback) alle API.
Vediamo quindi nel dettaglio come gestire le nozioni precedentemente apprese. Lato API, vediamo un semplice esempio in cui vogliamo come output un oggetto di prova contenente id e descrizione:
$obj = new stdClass();
$obj->id = "56321564";
$obj->descrizione = "Prova di descrizione";

if ($_GET['callback']!='') {
    echo $_GET['callback'].'('.json_encode($obj).')';
} 
else {
    echo json_encode($obj);
}
In questo modo siamo in grado di capire in maniera molto semplice (senza header http) in che formato mostrare il risultato: se esiste un parametro GET chiamato callback, lo script deve utilizzare il formato JSONP, altrimenti il JSON normale.
Ed ora la parte frontend, in jQuery:
var apiUrl = "http://www.example.com/api/example.php";
$( document ).ready(function() {
    $.ajax({
        type: "GET",
        url: apiUrl,
        dataType: "jsonp" ,
        crossDomain: true,
    }).done(function( json_response ) {
        console.log(JSON.stringify(json_response));
    }).fail(function(jqXHR, textStatus) {
        $('#result').html( "Request failed: " + textStatus + " " + jqXHR.status );
    });

});
La quale produrrà il risultato voluto in console:
{"id":"56321564", "descrizione": "Prova di descrizione"}
Importanti sono l'opzione dataType nella chiamata Ajax impostata su jsonp e crossDomain impostato su true.

domenica 4 agosto 2013

Come far parlare il Raspberry Pi tramite php


Qualche tempo fa ho cominciato a divertirmi con il mio Raspberry Pi. Una delle prime cose che ho voluto fare è stata quella di far parlare il raspPi attraverso il sintetizzatore vocale. In che modo? Con una pagina php richiamata dall'utente.
Per realizzare il tutto, basta installare un webserver (lighttpd nel mio caso) con interprete php5 e il sintetizzatore vocale (espeak), configurando i diritti della www-root:

sudo apt-get install lighttpd
sudo apt-get install php5-common php5-cgi php5
sudo lighty-enable-mod fastcgi-php
sudo service lighttpd force-reload
sudo chown www-data:www-data /var/www
sudo chmod 775 /var/www
sudo apt-get install espeak
Facciamo una prova, poi eseguiamo visudo per dare i diritti di esecuzione all'utente www-data attraverso visudo
espeak -vit "Prova di testing"
sudo visudo
Lo so che è un incredibile errore di sicurezza, ma tanto il mio RaspberryPi sta solo in casa e quindi per facilità ho abilitato l'utente a poter eseguire con sudo e senza password qualsiasi comando. Aggiungiamo quindi la seguente riga nel visudo
www-data ALL=(ALL) NOPASSWD: ALL
Creiamo una pagina php sotto la cartella /var/www con il seguente contenuto:
<?php 
echo exec(" sudo /usr/bin/espeak -vit \"Questo messaggio viene riprodotto ogni volta che si visita la pagina da un browser\"", $out, $out2);
?>
Questo ovviamente apre uno scenario fantastico relativo alla domotica :-)

sabato 3 agosto 2013

C# - programma per l'update automatico degli eseguibili

Ho creato qualche tempo fa un piccolo e semplice programma in C# che si occupa di fare da "loader" per le applicazioni. Ad esempio ipotizziamo di avere una applicazione HelloWorld; il loader (un altro eseguibile) si comporterà come segue:
  • appena viene eseguito, controlla sul server (tramite un percorso di rete windows) se la versione dell'applicazione HelloWorld presente in locale sia meno recente di quella sul server. Se così è, scarica la versione più recente dal percorso (è possibile specificare dalla configurazione se aggiornare una intera cartella o solo l'eseguibile).
  • una volta finito il primo punto, esegue l'applicazione HelloWorld che si è copiato in locale.
Tutto il comportamento è racchiuso dentro un BackgroundWorker (un semplice oggetto per gestire un thread in C#). Vediamo nel dettaglio il comportamento:
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                FileInfo fi1 = 
                         new FileInfo(Properties.Settings.Default.eseguibileRemoto);
                FileInfo fi2 = 
                         new FileInfo(Properties.Settings.Default.eseguibileLocale);
                //Se la directory locale non esiste, la creo
                if (!(Directory.Exists(Properties.Settings.Default.cartellaLocale)))
                {
                    Directory.CreateDirectory(Properties.Settings.Default.cartellaLocale);
                }
                //Controllo che il timestamp di modifica del file eseguibile remoto sia
                //più recente di quello locale
                if (fi1.LastWriteTime.Ticks > fi2.LastWriteTime.Ticks)
                {
                    if (Properties.Settings.Default.copiaTutto)
                    {
                        //Devo copiare tutto il contenuto della directory in locale
                        foreach (string fileTrovato in Directory.GetFiles(Properties.Settings.Default.cartellaRemota))
                        {
                            FileInfo FTEMP = new FileInfo(fileTrovato);
                            //Copio il file remoto in locale
                            File.Copy(fileTrovato, Properties.Settings.Default.cartellaLocale + FTEMP.Name, true);
                        }
                    }
                    else
                    {
                        //Procedura per la copia solo di alcuni files (ad esempio, eseguibili Visual Fox)
                        foreach (string nomeFile in Properties.Settings.Default.copiaSoloQuestiFiles)
                        {
                            FileInfo FTEMP = new FileInfo(nomeFile);
                            File.Copy(Properties.Settings.Default.cartellaRemota + nomeFile, Properties.Settings.Default.cartellaLocale + nomeFile, true);
                        }
                    }

                }
   
            }
            catch (Exception ex) { }
        }
Appena terminato il lavoro (RunWorkerCompleted), l'applicazione verrà eseguita e il loader verrà terminato:

        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            //Al termine della copia, mi preparo ad eseguire il programma
            ProcessStartInfo p = new ProcessStartInfo();
            p.FileName = Properties.Settings.Default.eseguibileLocale;
            p.Arguments = Properties.Settings.Default.parametri;
            //Se è impostato "eseguiDa" (ad esempio per JT) eseguo il programma dal percorso impostato
            if (Properties.Settings.Default.eseguiDa.Length > 0) p.WorkingDirectory = Properties.Settings.Default.eseguiDa;
            //Altrimenti uso la cartella locale
            else p.WorkingDirectory = Properties.Settings.Default.cartellaLocale;
            try
            {
                //Eseguo il programma
                Process.Start(p);
            }
            catch (Exception exep)
            {
                MessageBox.Show("Impossibile avviare il programma richiesto\r\n" + exep.Message);
            }
            Application.Exit();
        }
Le impostazioni possibili nella configurazione del loader sono le seguenti:


Il codice sorgente può essere liberamente scaricabile da Github (qui il link del progetto sul mio sito personale)

sabato 27 luglio 2013

Android: backup e restore di un database sqlite

Per l'applicazione Mie Ricette mi sono trovato a risolvere un problema: dare la possibilità all'utente di eseguire un backup del database delle ricette.
Il database usato nello sviluppo Android è Sqlite, quindi è essenzialmente un file che può essere copiato in una posizione "sicura" come la scheda SD, o comunque un percorso accessibile collegando il telefono al computer.
Nello script quindi sono presenti due funzioni, backupDatabase e restoreDatabase. Il primo l'ho trovato in qualche blog (perdonatemi, non mi ricordo quale). Entrambi utilizzano i FileChannel, ma mentre il cuore del funzionamento del primo si ha nella funzione nativa FileChannel.transferFrom, nel secondo specularmente si ha in FileChannel.transferTo.

package net.stefanobianchini.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import net.stefanobianchini.ricette.R;

import android.app.Activity;
import android.content.res.Resources;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;

public class backupdb {

 public static void backupDatabase(String dbPath, String nomeFileBackup,
   Activity act, Resources res) {

  try {
    File currentDB = new File(dbPath);// path del db su telefono
    File backupDB = new File(sd, nomeFileBackup);// file di destinazione
    @SuppressWarnings("resource")
    FileChannel src = new FileInputStream(currentDB)
       .getChannel();// apriamo un filechannel sul db e sul
           // file di destinazione
    @SuppressWarnings("resource")
    FileChannel dst = new FileOutputStream(backupDB)
       .getChannel();
    dst.transferFrom(src, 0, src.size());// trasferiamo il contenuto
    src.close();
     dst.close();
     Toast.makeText(
       act.getBaseContext(),

         res.getString(R.string.msg_db_location) + "\n " +
         backupDB.getAbsolutePath(),
       Toast.LENGTH_LONG).show();

  } catch (IOException e) {
   Toast.makeText(act.getBaseContext(), e.toString(),
     Toast.LENGTH_LONG).show();
  }
 }

 @SuppressWarnings("resource")
 private static void copyFile(File src, File dst) throws IOException {
  FileChannel inChannel = new FileInputStream(src).getChannel();
  FileChannel outChannel = new FileOutputStream(dst).getChannel();
  try {
   inChannel.transferTo(0, inChannel.size(), outChannel);
  } finally {
   if (inChannel != null)
    inChannel.close();
   if (outChannel != null)
    outChannel.close();
  }
 }

 public static void restoreDatabase(String dbPath, String percorsoFileBackup,
   Activity act, Resources res) {

  try {
   File currentDB = new File(dbPath);// path del db su telefono
   File backupDB = new File(percorsoFileBackup);// file di backup sorgente

   copyFile(backupDB, currentDB);

   Toast.makeText(
     act.getBaseContext(),
     String.format(res.getString(R.string.restore_done),
       backupDB.toString()), Toast.LENGTH_LONG).show();

  } catch (IOException e) {
   Toast.makeText(act.getBaseContext(), e.toString(),
     Toast.LENGTH_LONG).show();
  }
 }

}
Ora siamo pronti per eseguire il backup. Da notare che prima di eseguire il backup viene chiuso per sicurezza il db mediante il SQLiteOpenHelper (nel mio caso chiamato db_help);
//Backup del DB sqlite
db_help.close();
backupdb.backupDatabase(db_help.getReadableDatabase().getPath(), "backup_mie_ricette.db", HomeActivity.this, res);

venerdì 26 luglio 2013

C# Reflection, ovvero come richiamare librerie DLL a runtime


Per lavoro ultimamente mi son trovato a sviluppare un programma per la vendita al dettaglio di cosmesi. Le tecnologie scelte sono state C# (serviva un qualcosa solo su sistema Windows a finestre) e MySql (varie postazioni, sistema distribuito).
La sfida si è presentata quando ho dovuto progettare un sistema dinamico per gestire i registratori di cassa. Questo perché i registratori di cassa sono principalmente di due tipi, "Sweda" e "Ditron" con connessioni diverse e soprattutto comandi diversi. Lo Sweda ha una seriale rs232 mentre il Ditron è sempre collegato in seriale ma si appoggia su un programmino demone che fa il lavoro di comunicazione leggendo un determinato file (invio.txt) in una determinata cartella.
Per riassumere: per uno il C# deve aprire una connessione seriale, per l'altro creare un file. Entrambi hanno una sintassi completamente diversa.
Come fare per ottenere una progettazione "dinamica" che renda facile una successiva espansione, ad esempio l'inserimento di un altro tipo di registratore di cassa con un suo interfacciamento?

La soluzione intrapresa si basa sulla "Reflection", ossia sulla capacità del C# di caricare una DLL esterna a runtime (e gli oggetti e metodi in essa contenuti, quindi senza che sia per forza inclusa nel progetto), tenendo così separati il progetto della vendita al dettaglio dai singoli progetti C# delle librerie di interfacciamento ai registratori di cassa.
In questo modo, lo sviluppo delle suddette librerie dei registratori di cassa può essere anche lasciato ad un altra persona (o addirittura ditta).
La soluzione è quindi scalabile e si basa sul creare librerie con un nome differente (es. sweda.dll e ditron.dll) e comportamento differente ma con all'interno un oggetto chiamato RegistratoreFiscale, le cui funzioni pubbliche sono identiche in entrambe le librerie.
Vediamo un esempio di scheletro di libreria:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO.Ports;
using System.Windows.Forms;

namespace Sweda
{
    public static class RegistratoreFiscale
    {
        public static SerialPort serialPort = null;
        public static void inizializza(string opzioni_di_inizializzazione) {}
        public static void aggiungiVoceAcquisto(double prezzo, string reparto, string descrizione, double prezzo_originale) {}
        public static void scegliOperatore(string operatore) {}
        public static void chiusuraScontrino(double totale) {}
     }
}
A questo punto la libreria Ditron sarà esattamente identica (o altre librerie in futuro che gestiranno altri registratori), quello che cambia è ovviamente l'implementazione dei metodi pubblici sopracitati.

Quello che si deve predisporre ora nel progetto principale (quello di vendita al dettaglio) è un oggetto "standard" che riceverà le chiamate ai metodi e si preoccuperà di richiamare questi metodi sulla libreria opportuna (sweda per i pc che hanno uno sweda collegato, ditron per i pc che hanno un ditron collegato, ecc.). Fondamentale la direttiva using System.Reflection per usare la caratteristica chiave di questa soluzione progettuale:

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace CosmeticSeller.oggetti
{
    class RegistratoreFiscale
    {
        private Type oggettoRegistratoreRuntime;

        public RegistratoreFiscale(string tipo_registratore)
        {
            Assembly SampleAssembly;
            SampleAssembly = Assembly.LoadFrom(tipo_registratore + ".dll");
            AppDomain.CurrentDomain.Load(SampleAssembly.GetName());
            foreach (Type t in SampleAssembly.GetTypes())
            {
                if (t.Name.Equals("RegistratoreFiscale"))
                {
                    oggettoRegistratoreRuntime = t;
                }
            }
        }

        public void aggiungiVoceAcquisto(double prezzo, string reparto, string descrizione, double prezzo_originale)
        {
            MethodInfo Method = oggettoRegistratoreRuntime.GetMethod("aggiungiVoceAcquisto");

            List>object< listaParametri = new List>object<();
            listaParametri.Add(prezzo);
            listaParametri.Add(reparto);
            listaParametri.Add(descrizione);
            listaParametri.Add(prezzo_originale);
            Method.Invoke(this, listaParametri.ToArray());
        }

        public void inizializza(string opzioni_di_inizializzazione)
        {
            MethodInfo Method = oggettoRegistratoreRuntime.GetMethod("inizializza");
            List<object> listaParametri = new List<object>();
            listaParametri.Add(opzioni_di_inizializzazione);
            Method.Invoke(this, listaParametri.ToArray());
        }

        public void chiusuraScontrino(double totale)
        {
            MethodInfo Method = oggettoRegistratoreRuntime.GetMethod("chiusuraScontrino");

            List<object> listaParametri = new List<object>();
            listaParametri.Add(totale);
            Method.Invoke(this, listaParametri.ToArray());
        }

        public void scegliOperatore(string operatore)
        {
            MethodInfo Method = oggettoRegistratoreRuntime.GetMethod("scegliOperatore");

            List<object> listaParametri = new List<object>();
            listaParametri.Add(operatore);
            Method.Invoke(this, listaParametri.ToArray());
        }
    }
}
Volendo descrivere questa classe in due parole, il suo compito è essere un wrapper tra il programma di vendita  e la libreria da utilizzare. Il cuore del funzionamento è nel costruttore, dove viene caricata dinamicamente la dll a partire dal nome passato come parametro. Nella libreria dll viene quindi cercato l'oggetto RegistratoreFiscale (implementato in tutte le librerie) e caricato in memoria come oggetto Type (oggettoRegistratoreRuntime).
L'oggetto deve essere di tipo Type poiché il compilatore non è in grado di capire che oggetto sia (sappiamo però quali metodi abbiamo implementato e possiamo richiamarli).
Nelle varie funzioni infatti, le quali mappano le implementazioni nelle varie librerie, viene eseguita l'invocazione del metodo desiderato a runtime (Method.Invoke). Durante l'invocazione è possibile passare anche i parametri al metodo desiderato (sotto forma di array di object).

A questo punto è possibile dal programma principale evocare il wrapper a seconda del registratore fiscale utilizzato (questa informazione nel mio caso è salvata su una tabella "impostazioni" su DB).
...
RegistratoreFiscale reg = new RegistratoreFiscale("Sweda");
reg.inizializza("COM1");
for( ... ) { //Ciclo tutti gli acquisti
    reg.aggiungiVoceAcquisto(prezzoComplessivo, reparto, descrizione, totaleriga);
}
reg.chiusuraScontrino(importo_totale);
...

giovedì 4 aprile 2013

FitStadium - Un social network italiano per stare in forma

Oggi vi parlo di FitStadium, un vero e proprio social network sul fitness e l'attività fisica nato per aiutare gli utenti a migliorare la propria forma fisica. Tre ragazzi italiani, appassionati di cultura fisica e fitness, sono dietro quest'idea e stanno creando una startup appositamente per questo progetto.

Dal punto di vista tecnico, i creatori hanno deciso di usare una buona base open source "sociale" sviluppato in Php, ossia Elgg, stravolgendone però completamente il funzionamento, aggiungendo tutte le caratteristiche che rendono FitStadium un prodotto (gratuito!) di tutto rispetto:

  • Ha una bacheca tipica dei social network, dove un utente può interagire con gli altri utenti e confrontarsi
  • Permette agli utenti di inserire (e tenere traccia) dei propri allenamenti delle categorie più varie (attività cardio, fitness, bodybuilding, sollevamento pesi, attività fisiche all'aperto e tantissime altre). Per intenderci, questa pagina elenca tutto il loro database esercizi, costantemente in aggiornamento.
  • Gli allenamenti inseriti possono essere mostrati agli altri utenti (a seconda delle proprie impostazioni privacy) per chiedere consigli e pareri
  • E' provvisto di un buon motore statistico per tener traccia dei propri miglioramenti (o peggioramenti :-D)
  • Se un utente accede con facebook, gli allenamenti possono essere pubblicati anche nella bacheca di facebook 
  • Classifiche mensili per le varie categorie di allenamento
  • Ha una comunità interna molto attiva
  • Ha un sistema di chat e messaggistica interna
  • Si possono trovare degli speciali utenti Personal Trainer
  • Tra pochissimo pubblicheranno le app mobile (per gli utenti iscritti è già disponibile una beta, per Android e IOS), sviluppate con il framework PhoneGap e Jquery Mobile
Oltre a tutto questo il portale è in continua evoluzione, e gli sviluppatori aggiungono molto spesso caratteristiche nuove.



Se siete alla ricerca di motivazioni, di un metodo (pseudo) ingegneristico per migliorare la vostra forma fisica, o semplicemente avete bisogno di una comunità attiva da cui attingere tante informazioni sul fitness... iscrivetevi!

Cosa posso dire di più... in bocca al lupo e complimenti ai creatori!