Ruby 1.9 and the SSL error

  December 30th, 2010

After switching to Ruby 1.9 on Mac OS X 10.6, the following code which makes a https request to encrypted.google.com:

require 'net/https'
https = Net::HTTP.new('encrypted.google.com', 443)
https.use_ssl = true
https.verify_mode = OpenSSL::SSL::VERIFY_PEER
https.request_get('/')

fails with the following error:

/opt/local/lib/ruby1.9/1.9.1/net/http.rb:677:
  in `connect':
  SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed (OpenSSL::SSL::SSLError)

The problem comes from the fact that the new Ruby 1.9 installation doesn’t find the certification authority certificates (CA Certs) used to verify the authenticity of secured web servers.

The solution is to install the curl-ca-bundle port which contains the same root certificates used by Firefox:

sudo port install curl-ca-bundle

and tell your https object to use it:

https.ca_file = '/opt/local/share/curl/curl-ca-bundle.crt'

Note that if you want your code to run on Ubuntu, you need to set the ca_path attribute instead, with the default certificates location /etc/ssl/certs.

In the end, that’s what will work on both Mac OS X and Ubuntu:

require 'net/https'
https = Net::HTTP.new('encrypted.google.com', 443)
https.use_ssl = true
https.verify_mode = OpenSSL::SSL::VERIFY_PEER
https.ca_path = '/etc/ssl/certs' if File.exists?('/etc/ssl/certs') # Ubuntu
https.ca_file = '/opt/local/share/curl/curl-ca-bundle.crt' if File.exists?('/opt/local/share/curl/curl-ca-bundle.crt') # Mac OS X
https.request_get('/')

Lighttpd, MacPorts and the SSL error

  August 5th, 2010

After installing lighttpd on my Mac via MacPorts with the following command:

sudo port install lighttpd +ssl +cml

I get the following error:

(network.c.529) SSL: error:00000000:lib(0):func(0):reason(0)

The fix is to patch lighttpd before MacPorts compiles it:

sudo port uninstall lighttpd +ssl +cml
sudo port fetch lighttpd +ssl +cml
sudo port -d extract lighttpd +ssl +cml
cd  /opt/local/var/macports/build/_opt_local_var_macports_sources_rsync.macports.org_release_ports_www_lighttpd/work/lighttpd-1.4.26
curl http://redmine.lighttpd.net/attachments/download/1095/08-ssl-retval-fix.patch | sudo patch -p1
cd / && sudo port install lighttpd +ssl +cml

The SSL error is now gone!

SC.$ doesn’t support complex selectors

  July 12th, 2010

SC.$ doesn’t support complex selectors for performance reasons. For example, the following queries will return an empty or incorrect result set:

SC.$('.foo .bar:first-child')
SC.$(':empty')
SC.$('foo > bar')

If you don’t need your application to be compatible with IE 7 and Firefox 3.0, you can use the querySelectorAll method to overcome that limitation:

MyApp.$ = function(str) {
  return SC.$(document.querySelectorAll(str));
}

Capitaine Train

  June 6th, 2010

Capitaine Train is the Internet travel agency I co-founded a year ago in Paris, France. Capitaine Train only sells train tickets and is focused on providing a great user experience with a simple yet effective SproutCore-based web application.

We haven’t launched yet, but here is a preview. Stay tuned!

Testing and auto-generated ids in SproutCore

  January 27th, 2010

If you’re trying to automate tests of your SproutCore application with Selenium, for example, you’ll realise that the HTML element ids are automatically generated. They change every now and then and break all your tests.

To get rid of this problem, you can override the layerId method of certain view classes to generate a stable, and human readable, id.

The following code takes the view hierarchy, and generates an id based on the parents’ names. For example, the mainPage.mainView.someOtherView.theTargetView view will be given the mmsome.theTargetView id.

CT._ct_layerId = function() {
  var k1, k2, k3, k4, guid;

  if (!this._ct_layerId_cache) {
    guid = SC.guidFor(this);

    // Compute the new value
    k1 = this.get('keyForParentView');

    // If k1 is null, defer the computation to later...
    if (!k1) return SC.guidFor(this);

    // Finish the computation
    k2 = this.getPath('parentView.keyForParentView') || '';
    k3 = this.getPath('parentView.parentView.keyForParentView') || '';
    k4 = this.getPath('parentView.parentView.parentView.keyForParentView') || '';

    // Cache the result
    this._ct_layerId_cache = k4.substr(0,1) + k3.substr(0,1) + k2.substr(0,4) + '.' + k1;

    // If the id is already taken, fallback to guid
    if (SC.View.views[this._ct_layerId_cache]) {
      this._ct_layerId_cache = guid;

    // else, update the SC.View.views hash with the new value
    } else {
      delete SC.View.views[guid];
      SC.View.views[this._ct_layerId_cache] = this;
    }
  }

  return this._ct_layerId_cache;
}.property();

SC.View.prototype.mixin({

  keyForParentView: function() {
    var parentView = this.get('parentView'),
        key;
    for (key in parentView) {
      if (this === parentView[key]) return key;
    }
    return null;
  }.property()

});

SC.TextFieldView.prototype.mixin({
  layerId: CT._ct_layerId
});

SC.LabelView.prototype.mixin({
  layerId: CT._ct_layerId
});

SC.ImageView.prototype.mixin({
  layerId: CT._ct_layerId
});

Monkey patching SproutCore

  November 16th, 2009

You can add or overwrite a class property using the mixin method on the class prototype. The following example dynamically adds a keyForParentView method on the SC.View class without modifying SproutCore’s source code:

SC.View.prototype.mixin({
  keyForParentView: function() {
    var parentView = this.get('parentView'),
        key;
    for (key in parentView) {
      if (this === parentView[key]) return key;
    }
    return null;
  }
});

SC.Response and JSON

  November 13th, 2009

SproutCore’s SC.Response was recently rewritten and I patched it to avoid an exception to be thrown when a malformed JSON string was parsed. The standard way to write your data source didFetch method is now:

didFetch: function(response, params) {
  var results, query = params.query;
  if (SC.ok(response) && SC.ok(results = response.get('body'))) {
    ...
  } else {
    store.dataSourceDidErrorQuery(query);
  }
}