divendres, 30 de juny del 2017

"Màgia" amb els bits dels WebSockets

Per un projecte que estem desenvolupant a Amidasoft, he hagut d'implementar una comunicació entre Javascript i un servidor en Java via WebSockets. La veritat és que és senzill d'implementar (com tota comunicació via Sockets amb llenguatges d'alt nivell), però hi ha una particularitat que m'ha fet perdre algunes hores: l'alineament dels primers bits del frame de resposta des del servidor.

Segons la documentació, el frame és:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+

Això dóna a entrendre que, si estem parlant de paquets petits (menys de 128 bytes), el seu primer byte serà:
     10000001    => 0xA1
Ja que es tracta del darrer paquet i el seu opcode és 1. No sé per quina raó, amb aquest primer byte el navegador no és capaç d'interpretar bé el frame.

Al final, ho he solucionat canviant aquest byte per 0x81, tal com s'indica a l'exemple de http://bitsofgyan.com/index.php/2015/10/18/writing-a-simple-websocket-server-in-java/

No tinc clar perquè, però així funciona.

dimarts, 25 d’abril del 2017

Recaptcha "site-info-loading-failed"

Fa uns dies que un lloc web que vaig construir amb Wordpress ja fa temps té problemes amb els formularis de contacte.
Avui m'hi he pogut dedicar una mica i veig que el problema ve amb el reCAPTCHA de Google (v1), que sempre retorna:

false
site-info-loading-failed


No sé perquè ha començat a passar... El més sorprenent és que si es busca aquest error a Google, només troba 3 resultats, que són, precissament, aquest mateix missatge d'error, sense més explicació...

Què deu estar passant?

dimarts, 4 d’abril del 2017

Table './bdd_name/table_name' is marked as crashed and last (automatic?) repair failed

Avui m'ha aparegut aquest missatge en anar a fer una query a la base de dades MySql.
Segons he trobat a http://stackoverflow.com/questions/8843776/mysql-table-is-marked-as-crashed-and-last-automatic-repair-failed, el que cal fer és:

1. Aturar el servidor de MySql:
sudo service mysql stop

2. Anar al directori on està la base de dades (en el meu cas, només l'usuari root tenia permís per a accedir-hi):
cd /var/lib/mysql/$DATABASE_NAME

3. Reparar la taula:
myisamchk -r $TABLE_NAME

4. Tornar a iniciar la base de dades:
sudo service mysql stop

Amb això ja funciona correctament.

El que no sé és perquè ha passat...



dijous, 30 de març del 2017

Error a l'enviar un email des de Odoo v9.

Avui he trobat un problema en una instal·lació de Odoo 9 d'un client: en intentar enviar un email (amb el SMTP correctament configurat) dóna l'error:
Failed to render template <Template memory:7f001e0240d0> using values {'format_tz': <function <lambda> at 0x7efff4974f50>, 'ctx': {'record_name': False, u'uid': 1, 'button_access': None, 'thread_model': u'mail.message', 'safe': False, 'actions': [], 'not_followers': res.partner(11065,), u'params': {}, 'button_unfollow': False, u'lang': u'es_ES', 'tracking': [], u'tz': u'Europe/Andorra', u'active_model': u'mail.message', u'default_no_auto_thread': False, 'mail_post_autofollow': True, 'followers': res.partner(), 'company_name': u'XXXX, SL.', 'signature': u'<p><span>--<br></span></p><p></p><p>\nAdministrator</p>', 'button_follow': False, 'model_name': False, 'website_url': u'http://www.xxxx.com'}, 'user': res.users(1,), 'object': mail.message(32735,)}

Buscant una possible solució, he trobat https://groups.google.com/forum/#!topic/openerp-spain-users/cGNMjLRzEJY , on indiquen que el problema es corregeix posant l'idioma anglès... molt estrany, no?

Malhauradament, configurar l'Odoo per a que estigui en anglès no és una solució vàlida en el meu cas.
Afegint missatges de log a l'arxiu "/usr/lib/python2.7/dist-packages/openerp/addons/mail/models/mail_template.py", he vist que usa el següent text com a template:

<div itemscope itemtype="http://schema.org/EmailMessage">
  <div itemprop="potentialAction" itemscope itemtype="http://schema.org/ViewAction">
    % if ctx.get('button_access'):
      <link itemprop="target" href="${ctx['button_access']['url']}"/>
      <link itemprop="url" href="${ctx['button_access']['url']}"/>
    % endif
    <meta itemprop="name" content="Ver ${ctx['model_name']}"/>
  </div>
</div>
<div summary="o_mail_notification" style="padding:0px; width:600px; margin:0 auto; background: #FFFFFF repeat top /100%; color:#777777">
  <table cellspacing="0" cellpadding="0" style="width:600px; border-collapse:collapse; background:inherit; color:inherit">
    <tbody>
      <tr>
        <td valign="center" width="270" style="padding:5px 10px 5px 5px;font-size: 30px">
          % if ctx.get('button_access'):
            <a href="${ctx['button_access']['url']}" style="-webkit-user-select: none; padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#a24689; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #a24689; border: 1px solid #a24689; border-radius:3px" class="o_default_snippet_text">
              ${ctx['button_access']['title']}
            </a>
          % endif
          % if ctx.get('button_follow'):
            <a href="${ctx['button_follow']['url']}" style="-webkit-user-select: none; padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#a24689; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #a24689; border: 1px solid #a24689; border-radius:3px" class="o_default_snippet_text">
              ${ctx['button_follow']['title']}
            </a>
          % elif ctx.get('button_unfollow'):
            <a href="${ctx['button_unfollow']['url']}" style="-webkit-user-select: none; padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#a24689; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #a24689; border: 1px solid #a24689; border-radius:3px" class="o_default_snippet_text">
              ${ctx['button_unfollow']['title']}
            </a>
          % endif
          % if not ctx.get('button_access') and not ctx.get('button_follow') and not ctx.get('button_unfollow') and ctx.get('model_name'):
            <p style="padding: 5px 10px; font-size: 12px;">
              Sobre <strong>${ctx['model_name']} % if ctx.get('record_name'): : ${ctx['record_name']} % endif </strong>
            </p>
          % endif
        </td>
        <td valign="center" align="right" width="270" style="padding:5px 15px 5px 10px; font-size: 12px;">
          <p>
            % if ctx.get('actions'):
              % for action in ctx['actions']:
                <a href="${action['url']}" style="text-decoration:none; color: #a24689;">
                  <strong>${action['title']}</strong>
                </a>
                %if cmp(len(ctx['actions']), 1) == 1 and cmp(len(ctx['actions']), loop.index) == 1:
                  |
                % endif
              % endfor
            % else:
                <strong>Enviado por</strong>
              % if ctx.get('website_url'):
                <a href="${ctx['website_url']}" style="text-decoration:none; color: #a24689;">
              % endif
              <strong>${ctx.get('company_name')}</strong>
              % if ctx.get('website_url'):
                </a>
              % endif
              <strong>usando</strong>
              <a href="https://www.odoo.com" style="text-decoration:none; color: #a24689;">
                <strong>Odoo</strong>
              </a>
            % endif
          </p>
        </td>
      </tr>
    </tbody>
  </table>
</div>
<div style="padding:0px; width:600px; margin:0 auto; background: #FFFFFF repeat top /100%; color:#777777">
  <table cellspacing="0" cellpadding="0" style="vertical-align:top; padding:0px; border-collapse:collapse; background:inherit; color:inherit">
    <tbody>
      <tr>
        <td valign="top" style="width:600px; padding:5px 10px 5px 5px;">
          <div>
            <hr width="100%" style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0;margin:15px auto;padding:0">
          </div>
        </td>
      </tr>
    </tbody>
  </table>
</div>
<div style="padding:0px; width:600px; margin:0 auto; background: #FFFFFF repeat top /100%;color:#777777">
  <table cellspacing="0" cellpadding="0" border="0" style="margin: 0 auto; width:600px; border-collapse:collapse; background:inherit; color:inherit">
    <tbody>
      <tr>
        <td style="padding:5px 10px 5px 5px;font-size: 14px;">
          ${object.body | safe}
          % if ctx.get('tracking'):
            <ul>
              % for tracking in ctx['tracking']
                <li>${tracking[0]} : ${tracking[1]} -&gt; ${tracking[2]}</li>
              % endfor
            </ul>
          % endif
        </td>
      </tr>
    </tbody>
  </table>
</div>
% if ctx.get('signature'):
  <div style="padding:0px; width:600px;margin:0 auto; background: #FFFFFF repeat top /100%;color:#777777">
    <table cellspacing="0" cellpadding="0" border="0" style="margin: 0 auto; width:600px; border-collapse:collapse; background:inherit; color:inherit">
      <tbody>
        <tr>
          <td style="padding:5px 10px 5px 5px;font-size: 14px; text-align: left;">
            ${ctx['signature'] | safe}
          </td>
        </tr>
      </tbody>
    </table>
  </div>
% endif
Es correspon a la plantilla de mail amb nom "Notification Email". En canviar aquesta plantilla per només:
  ${object.body | safe}

El missatge s'envia correctament, pel que el problema està en el contingut de la plantilla.
L'he eliminat i he anat afegint fragments fins que... misteri! Ha funcionat amb tota la plantilla!

L'única diferència que hi sé veure és que inicialment estava tota en una sola línia, mentre que ara l'he partit tal com es veu més amunt, indentant els tags... Serà problema del buffer de línia?


Precisió decimal en un camp de Odoo (QWeb)

En afegir un camp a un mòdul d'Odoo, el sistema, per defecte, li assigna una precisió (a la vista) de dos decimals. És igual la precisió que tingui a la base de dades, sempre seran dos decimals.
Per a poder-ho modificar, cal indicar-ho al xml de la vista amb un atribut del tipus: digits="(16,7)"
Amb això tindrem que s'accepta un número de fins a 16 posicions, de les quals 7 són decimals.

Un exemple complet de la vista heretada:

<openerp>
    <data>

     <record model="ir.ui.view" id="add_field_partner_tree">
         <field name="name">res.partner.form.inherit</field>  
         <field name="model">res.partner</field>
         <field name="inherit_id" ref="base.view_partner_form"/>
         <field name="arch" type="xml">
           <xpath expr="//field[@name='lang']" position="after"> 
             <field name="descompte_defecte" />
           </xpath>
           <xpath expr="//field[@name='category_id']" position="after"> 
             <field name="factor_preus" digits="(16,7)" />
           </xpath>
         </field>
     </record>

    </data>

</openerp>

dijous, 6 de febrer del 2014

Crear un filtre en una WebApp en JSF per a canviar el tipus de lletra

Recentment un client m'ha demanat de canviar el tipus de lletra d'una aplicació. Es tracta d'un portal fet amb JSF en el que els usuaris poden crear els seus continguts. En la majoria d'aquests, han usat el tipus de lletra "Helvetica", sense tenir en compte que es tracta d'una font sotmesa a llicència i no disponible a tots els sistemes.

Per a corregir-ho, enlloc d'entrar a tots els articles i modificar-ne el tipus de lletra (amb el risc d'oblidar-ne algun), el que hem fet ha estat crear un filtre que modifiqui la sortida cap al navegador substituïnt el tipus de lletra per "Arial".

S'ha pogut fer mitjançant tres classes:
  • FiltreCanviarFontDeHelveticaAArial, el filtre encarregat del processament
  • HttpServletResponseAmbBuffer, un wrapper de la resposta que permet actuar sobre un buffer enlloc de l'stream de sortida.
  • ServletOutputStreamCopier, l'stream usat com a buffer
A continuació hi ha el codi de les tres classes:


FiltreCanviarFontDeHelveticaAArial.java

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;

/**
 *
 * @author Oscar Pérez del Campo 
 */
public class FiltreCanviarFontDeHelveticaAArial implements Filter
{

  private static final boolean debug = true;
  // The filter configuration object we are associated with.  If
  // this value is null, this filter instance is not currently
  // configured. 
  private FilterConfig filterConfig = null;

  public FiltreCanviarFontDeHelveticaAArial()
  {
  }

  private void doBeforeProcessing(ServletRequest request, ServletResponse response)
          throws IOException, ServletException
  {
    if (debug)
    {
      log("FiltreCanviarFontDeHelveticaAArial:DoBeforeProcessing");
    }

  }

  private void doAfterProcessing(ServletRequest request, HttpServletResponseAmbBuffer response)
          throws IOException, ServletException
  {
    if (debug)
    {
      log("FiltreCanviarFontDeHelveticaAArial:DoAfterProcessing");
    }

    byte[] dades = response.getCopy();
    String strResposta = new String(dades);
    if (!StringUtils.isEmpty(strResposta))
    {
      strResposta = strResposta.replaceAll("font-family:[ ]*helvetica", "font-family: arial");
      response.substitueixResposta(strResposta);
    }
    
  }

  /**
   *
   * @param request The servlet request we are processing
   * @param response The servlet response we are creating
   * @param chain The filter chain we are processing
   *
   * @exception IOException if an input/output error occurs
   * @exception ServletException if a servlet error occurs
   */
  public void doFilter(ServletRequest request, ServletResponse response,
          FilterChain chain)
          throws IOException, ServletException
  {

    if (debug)
    {
      log("FiltreCanviarFontDeHelveticaAArial:doFilter()");
    }

    OutputStream osOriginal = response.getOutputStream();
    
    HttpServletResponseAmbBuffer resp = new HttpServletResponseAmbBuffer((HttpServletResponse)response);
    doBeforeProcessing(request, resp);

    Throwable problem = null;
    try
    {
      chain.doFilter(request, resp);
    } catch (Throwable t)
    {
      // If an exception is thrown somewhere down the filter chain,
      // we still want to execute our after processing, and then
      // rethrow the problem after that.
      problem = t;
      t.printStackTrace();
    }

    doAfterProcessing(request, resp);
    
    osOriginal.write(resp.getCopy());

    // If there was a problem, we want to rethrow it if it is
    // a known type, otherwise log it.
    if (problem != null)
    {
      if (problem instanceof ServletException)
      {
        throw (ServletException) problem;
      }
      if (problem instanceof IOException)
      {
        throw (IOException) problem;
      }
      sendProcessingError(problem, resp);
    }
  }

  /**
   * Return the filter configuration object for this filter.
   */
  public FilterConfig getFilterConfig()
  {
    return (this.filterConfig);
  }

  /**
   * Set the filter configuration object for this filter.
   *
   * @param filterConfig The filter configuration object
   */
  public void setFilterConfig(FilterConfig filterConfig)
  {
    this.filterConfig = filterConfig;
  }

  /**
   * Destroy method for this filter
   */
  public void destroy()
  {
  }

  /**
   * Init method for this filter
   */
  public void init(FilterConfig filterConfig)
  {
    this.filterConfig = filterConfig;
    if (filterConfig != null)
    {
      if (debug)
      {
        log("FiltreCanviarFontDeHelveticaAArial:Initializing filter");
      }
    }
  }

  /**
   * Return a String representation of this object.
   */
  @Override
  public String toString()
  {
    if (filterConfig == null)
    {
      return ("FiltreCanviarFontDeHelveticaAArial()");
    }
    StringBuffer sb = new StringBuffer("FiltreCanviarFontDeHelveticaAArial(");
    sb.append(filterConfig);
    sb.append(")");
    return (sb.toString());
  }

  private void sendProcessingError(Throwable t, ServletResponse response)
  {
    String stackTrace = getStackTrace(t);

    if (stackTrace != null && !stackTrace.equals(""))
    {
      try
      {
        response.setContentType("text/html");
        PrintStream ps = new PrintStream(response.getOutputStream());
        PrintWriter pw = new PrintWriter(ps);
        pw.print("\n\nError\n\n\n"); //NOI18N

        // PENDING! Localize this for next official release
        pw.print("

The resource did not process correctly

\n
\n");
        pw.print(stackTrace);
        pw.print("
\n"); //NOI18N pw.close(); ps.close(); response.getOutputStream().close(); } catch (Exception ex) { } } else { try { PrintStream ps = new PrintStream(response.getOutputStream()); t.printStackTrace(ps); ps.close(); response.getOutputStream().close(); } catch (Exception ex) { } } } public static String getStackTrace(Throwable t) { String stackTrace = null; try { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); t.printStackTrace(pw); pw.close(); sw.close(); stackTrace = sw.getBuffer().toString(); } catch (Exception ex) { } return stackTrace; } public void log(String msg) { filterConfig.getServletContext().log(msg); } }


HttpServletResponseAmbBuffer.java

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

/**
 *
 * @author Oscar Pérez del Campo
 */
public class HttpServletResponseAmbBuffer extends HttpServletResponseWrapper
{

  private ServletOutputStream outputStream;
  private PrintWriter writer;
  private ServletOutputStreamCopier copier;

  public HttpServletResponseAmbBuffer(HttpServletResponse response) throws IOException
  {
    super(response);
  }

  @Override
  public ServletOutputStream getOutputStream() throws IOException
  {
    if (writer != null)
    {
      throw new IllegalStateException("getWriter() has already been called on this response.");
    }

    if (outputStream == null)
    {
      outputStream = getResponse().getOutputStream();
      copier = new ServletOutputStreamCopier(outputStream);
    }

    return copier;
  }

  @Override
  public PrintWriter getWriter() throws IOException
  {
    if (writer == null)
    {
      writer = new PrintWriter(new OutputStreamWriter(this.getOutputStream(), getResponse().getCharacterEncoding()), true);
    }

    return writer;
  }

  @Override
  public void flushBuffer() throws IOException
  {
    if (writer != null)
    {
      writer.flush();
    } else if (outputStream != null)
    {
      copier.flush();
    }
  }

  public byte[] getCopy()
  {
    if (copier != null)
    {
      return copier.getCopy();
    } else
    {
      return new byte[0];
    }
  }

  public void substitueixResposta(String str) throws IOException
  {
    copier.substitueixContinguts(str);
  }
  
}


ServletOutputStreamCopier.java

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.ServletOutputStream;

/**
 *
 * @author Oscar Pérez del Campo 
 */
public class ServletOutputStreamCopier extends ServletOutputStream
{

  private OutputStream outputStream;

  public ServletOutputStreamCopier(OutputStream outputStream)
  {
    this.outputStream = new ByteArrayOutputStream(2048);
  }

  @Override
  public void write(int b) throws IOException
  {
    outputStream.write(b);
  }

  public byte[] getCopy()
  {
    return ((ByteArrayOutputStream) outputStream).toByteArray();
  }

  void substitueixContinguts(String str) throws IOException
  {
    if (outputStream != null)
    {
      outputStream.close();
    }
    outputStream = new ByteArrayOutputStream();
    outputStream.write(str.getBytes());
  }
}

dimecres, 20 de novembre del 2013

Problemes amb els estils de newsletters a Gmail

A Amidasoft estem desenvolupant el portal d'Exposicions Itinerants de la Diputació de Girona (http://www.ddgi.cat/exposicions). Disn d'aquest portal, hi ha la utilitat d'enviament de newsletters amb les darreres novetats del mateix.

Fins aquí tot correcte, però amb les darreres proves, els usuaris han detectat que a Gmail (i altres webmails, com el OWA), tot el text apareix en color blau.

Debugant el sistema, hem vist que l'email que s'envia i el que mostra el Gmail són diferents: al fitxer origen, totes les etiquetes tenen un style="color:black" , mentre que, usant el Firebug, es pot comprovar que aquest atribut no apareix al mail rebut.

He trobat a http://www.emailonacid.com/blog/details/C13/12_things_you_must_know_when_developing_for_gmail_and_gmail_mobile_apps que el Gmail ignora l'atribut de color si aquest és "black", "#000" o "#000000".

Per tant, la solució és fàcil: canviar el style="color:black" per style="color:#010101"