Sunday 20 December 2009

SSL_Requirement

I use the ssl_requirement plugin to let me specify what actions need to be secure.

But - I found it limiting. I needed to:

  1. Specify what the secure domain was - we only have a certificate on the base domain, not all the subdomains

  2. Be able to easily turn it off in different environments


So, I forked a version off that does that. If you have the same needs (and, like me, prefer a plugin over a gem) check mine out!

Saturday 19 December 2009

Apache + Passenger + SSL + OSX

Recently I wanted to do some work on parameter passing and also how to keep parameters passed to SSL pages through redirects.
The first stumbling block was to get passenger and apache to play nice with SSL on OSX

I am running Leopard (not SNOW Leopard) and here is what I did to get it to work. I couldn't find anything specific on the net about this, so I thought I'd chuck this up here. There may be nicer, better ways of doing this.

1 - Create the cert


Apple has a page on creating a cert. This all worked fine except the locations aren't right. This page must be for Tiger.
For Leopard apache is in /etc/apache2 (or /private/etc/apache2 depending on your installation).
It all seemed to work fine for me as written apart from that.
You end up with a ssl.key directory in your apache2 directory. You may wish to rename this domain.ssl.key if you are doing multi domain development. I am, but this is the only domain I wanted to check ssl on.

2 - Apache and SSL


This turned out to be easier than expected, but everything's easy when you know how!
Edit /etc/apache2/httpd.conf
All I needed to do is put this line around line 40:
Listen 80
Listen 443


You add the Listen 443

3 - Passenger files


I did this by hand. As far as I know you can't do this via the prefpane,
I have used the PrefPane to create the vhosts file.
sudo vi passenger_pane_vhosts/mydomain.local.vhost.conf

Then add:

<VirtualHost *:443>
ServerName mydomain.local
ServerAlias mydomain.local es.mydomain.local en.mydomain.local
DocumentRoot "/Users/smyp/development/mydomain/public"
RailsEnv development
<directory "/Users/smyp/development/mydomain/public">
Order allow,deny
Allow from all
</directory>

# SSL Configuration
SSLEngine on
SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP
SSLOptions +FakeBasicAuth +ExportCertData +StdEnvVars +StrictRequire

#Self Signed certificates
SSLCertificateFile /private/etc/apache2/ssl.key/server.crt
SSLCertificateKeyFile /private/etc/apache2/ssl.key/server.key
SSLCertificateChainFile /private/etc/apache2/ssl.key/ca.crt

SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown downgrade-1.0 force-response-1.0

</VirtualHost>


Basically what you do is copy all the stuff from the area and then add in the extra SSL config.
Just point the crt, key and ca.crt files to the ones you created in step 1 from the apple doc.

That's it! You should be ready to go!

Let me know if there are any errors in this and I'll correct them... I wasn't making notes as I went along, so this is done from looking back, so maybe I've left something out.

Wednesday 16 December 2009

redirect_to : pass all parameters

On filmamora we redirect people to the language version we think they want. This is done with a subdomain for the language.
As if that wasn't painful enough, it now seems I was stripping out additional parameters - like the parameters passed in from our mailings that triggered Google Analytics campaigns.
Doh!

So this:
  def language_redirect
redirect_to :subdomain => session[:language] unless RAILS_ENV == 'staging'
end


Needed to become this:
  def language_redirect
p2 = params.merge({:subdomain => session[:language]})
redirect_to p2 unless RAILS_ENV == 'staging'
end


Such a small change - such a big difference! Marketing types appeased!

Tuesday 6 October 2009

rotating nginx logs

Since moving most of my systems over to plain Ubuntu on EC2 (as opposed to using ec2OnRails) I've needed to figure out certain things - like rotating the nginx logs (using Nginx instead of apache).

Simply create a file in /etc/logrotate.d/nginx that looks like this

/opt/nginx/logs/*.log {
daily
missingok
rotate 30
compress
delaycompress
sharedscripts
postrotate
/etc/init.d/nginx restart
endscript
}

Friday 24 July 2009

move from ec2onrails

Recently the performance on FilmAmora.com has been plummeting. And I had no idea why. Often apache was timing out waiting for a mongrel. Very little development has happened recently on FilmAmora, so I was stumped.
So - like a good developer - I took this as a chance to make a drastic move from ec2onrails machines to straight ubuntu, with everything else installed by hand.

It has made a HUGE difference. So much so that I could actually drop a machine from the configuration and still have better response times.

I followed these instructions to get started: rails-nginx-passenger-ubuntu. I also switched to nginx and ruby enterprise edition - just to make following the instructions easier.

So far so good. It took a couple hours and presto a leaner meaner installation. My NewRelic Apdex score went from .5 to .9.

Friday 17 July 2009

expiring action_cache

I was having a bugger of a time expiring my cache. The problem was the cache_path.
I have index actions that take filters. So, I created a cache_path for my action caching:

caches_action :index, :layout => false, :cache_path => Proc.new { |c| "#{c.params[:league_id]}/index/#{c.params.values.sort.to_s}" }


Which means that all my caches end up in a nice tree of 'views/[league name]'. But, how to expire a whole part of the cache tree when there could be dozens of nodes as people could be searching on last names too.

What I found was that expire_action was NOT the way to go. expire_fragment lets you pass in a regular expression. Now in my sweeper I have this:

  def expire_cache_for(player)
expire_fragment(%r{views/#{player.league.to_param}.*})
end


Which will expire all the cached code for the player's league.

Tuesday 23 June 2009

installing rmagick on osx leopard

I've put up a gist of a bash file that works (as of today) to do this from source.
You can clone it:

git clone git://gist.github.com/134823.git gist-134823

Sunday 7 June 2009

Ruby Toolbox

Just found this site (via a tweet)... seems to have a comprehensive list of rails extensions, broken down by category.
If only they linked to them!

Ruby Toolbox

Saturday 6 June 2009

Google's Page Speed

Google releases Page Speed - get it here - an open-source Firefox plugin similar to Yahoo's YSlow

i18n

We've (finally!) moved FilmAmora up to rails 2.2.2 - and not without some headaches.
The biggest ones seem to revolve around translations. We were using po/mo files and that worked great. Moving to 222 required some changing of gems (to fastgettext) and other things. But we were left without standard translations of all the ruby messages for some reason.

The guys at rails i18n.org came to the rescue!

Their Github project

Has config files for most locales that will cover all the standard messages. A real life saver!

Sunday 24 May 2009

installing the mysql gem under leopard

I am building a new dev laptop (an MSI Wind u100 running Leopard!) and got everything going... but getting the mysql gem to install was tricky. I had installed MySQL from a binary DMG install.
The trick was to install the gem like so:
sudo gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config

Forcing a different render format

I am writing an OAuth system for FilmAmora for the iphone app (and anything else I guess) to use.
I want to always output xml from the controller that is handling the catalog output... but how to do that!

I came up with this:

  def list
render :template => "/catalogs/list.xml.builder", :content_type => "text/xml"
end


This will always output xml (with the right mime type) no matter what the requested format was.

Monday 18 May 2009

Creating a new rails app with an old version

After much hunting I found how to do this

gem list rails

to see what versions you have. I need to create a 2.1.2 rails app. you can do that by issueing this command:

rails _2.1.2_ my_app

Sunday 10 May 2009

cookies and hosts / domains

I have been struggling for weeks with a problem of users being logged in to mysite.com, but not en.mysite.com and es.mysite.com. I posted on Ruby Groups and even tried to hire someone to do the work... but to no avail.
Finally I think I cracked it today.

Two things. First, for some reason doing this didn't work:
  if RAILS_ENV == 'production'
config.action_controller.session = {
:session_key => '_my_session',
:session_domain => '.mysite.com',
:secret => 'xxx'
}
else
config.action_controller.session = {
:session_key => '_my_session',
:session_domain => '.mysite.local',
:secret => 'xxy'
}
end


So, I substituted this into each environment file:
config.after_initialize do
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:session_domain] = '.mysite.local'
end


That got my session's having the correct domain set (.mysite.com) meaning that the session was saved across subdomains.

Cookies

But then I encountered a second problem. Cookies.
After switching to hide and seek / show and tell methods to enable me to page cache even pages that had logged in status all over them (more on this in another post) I needed some user info in a cookie. But my cookies were tied to the subdomain!
I was setting the cookies like this:

      cookies[:current_user] = {
:cart_items_count => cart_items_count,
:nothing => 1
}.to_json


I thought, ok I just need to add the domain in:
      cookies[:current_user] = {
:cart_items_count => cart_items_count,
:nothing => 1,
:domain => ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:session_domain]
}.to_json


No...this didn't work. Everything was being chucked into the cookies 'value' and not the domain field (same happened if I added in an expires_at key). What was going on?
Aha! It is the json-izing.

So, I moved that out to a seperate variable:
      value = {:cart_items_count => cart_items_count, :nothing => 1}.to_json
cookies.delete :login, :domain => ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:session_domain]
cookies[:current_user] = {
:value => value,
:domain => ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:session_domain]
}


Now my cookies had the proper domain info and the value was still proper json. And now my users are logged in across sub domains!

Saturday 21 March 2009

backgroundrb by hostname

I love backgroundrb - the way to run scheduled tasks and async workers. BUT on my setup I wanted to run certain backgroundrb tasks on one machine and certain ones on another (for memory reasons and also for testing).

I couldn't find something that did that so I forked backgroundrb into my own repository on GitHub. You can find it here.

You can now have a config file like this:
# A Sample YAML configuration file
---
:backgroundrb:
:ip: 0.0.0.0 #ip on which backgroundrb server is running
:port: 11006 #port on which backgroundrb server is running
:environment: production # rails environment loaded, defaults to development
:debug_log: true # whether to print debug logs to a seperate worker, defaults to true
:log: foreground # will print log messages to STDOUT, defaults to seperate log worker
# :result_storage: memcache # store results in a mecache cluster, you also need to specify location of your memcache clusters in next section
:persistent_disabled: true # turn this off if your application doesn't use backgroundrb's persistent/enqueued tasks system
:persistent_delay: 10 # the time (seconds) between each time backgroundrb checks the database for enqueued tasks
:per_environment: true

# You specify your worker schedules here
:whatever.local:
:backgroundrb:
:environment: test
:schedules:
:test_worker: # worker name
:test: #worker method
:trigger_args: */5 * * * * * * #worker schedule
:data: whatever

:phils-mac-pro.local:
:backgroundrb:
:environment: development
:schedules:
:test_worker: # worker name
:test: #worker method
:trigger_args: */5 * * * * * * #worker schedule
:data: phil


You can launch backgroundrb with a specific hostname like this:

./script/backgroundrb --hostname=whatever.local


I've created Capistrano tasks that fire up an instance on 2 servers and now they act differently based on hostname.

Sweet!

Friday 13 March 2009

Generating sitemaps and xml catalogues

One of the things we have to do at FilmAmora is generate a catalogue of our films. This is used in a few ways, but principally to drive our recommendations system.

I stumbled across this great blog entry:
Generating-Sitemaps-With-Rails that not only tells you a very nice way of generating sitemaps but also gave us great ideas for using the same technique to generate our catalogues.

Awesome!

Friday 13 February 2009

ec2onrails EU

Well, necessity is the mother of invention. We needed new FilmAmora servers to deal with the increasing load. I also wanted to move to EU-based EC2 instances.
That meant rolling our own because I believe there are no publicly available ec2onrails instances that run in the eu-west region.

This was not as easy as I would have liked, so here's a little step by step.

1. Go to Alestic and get the id of the proper eu instance. (At this writing it is ami-ac032bd8)

2. Fire up an instance of this. I use Elastic Fox and you need to set the region to europe then choose this instance.

3. copy your pem's into the instance:
scp -i IDENTITY {cert,pk}-*.pem root@HOSTNAME:/mnt/


4. ssh into the instance
ssh -i IDENTITY root@HOSTNAME


5. Get the build script
wget http://ec2ubuntu-build-ami.notlong.com


6. update so you can get git
apt-get update && apt-get upgrade -y


7. get git
apt-get install git-core


8. get ec2onrails and use the latest branch
git clone git://github.com/pauldowman/ec2onrails.git
cd ec2onrails
git checkout --track -b 0.9.9.1 origin/0.9.9.1


9. Run the build script!
 bash ec2ubuntu-build-ami \
--script /root/ec2onrails/server/build-ec2onrails.sh \
--user YOUR_ACCOUNT_NUMBER \
--access-key YOUR_ACCESS_KEY \
--secret-key YOUR_SECRET_KEY \
--bucket BUKCET_NAME \
--prefix PREFIX \
--location EU


Your account number is the 12 digit number from your amazon access information page.

10. Register the instance
ec2-register bucket/manifest.xml --region eu-west-1


That's it!
You should now be able to go to Elastic Fox and launch and instance of this that will be in Europe!

Wednesday 11 February 2009

DropDown menu for Blueprint tabbed menus

I love blueprint css! And I also love the tabs menu plugin for it. But, we neede drop down menus on FilmAmora. So I wrote a little extension that gives you that. It's not perfect, but, it works for now.

Find it here:
Blueprint_dd

Friday 6 February 2009

BackgroundRb and Synchronous calls

BackgroundRb lets you also call the workers synchronously. This is quite neat as you can use it as a form of pseudo-proxy and all other kinds of goodies.
But, the documentation is lacking, so I am posting this for all you frustrated Googlers out there. Here is what you need to do.

class SampleWorker < BackgrounDRb::MetaWorker
set_worker_name :sample_worker

def create(args = nil)
# this method is called, when worker is loaded for the first time
end

def test_me args
puts "test_me 1 (puts)"
logger.debug("test_me hit (log)")
return "of course it works"
end
end


Now, the BackgroundRb site tells you to call this like so to get a response back:

MiddleMan.worker(:sample_worker).test_me(:arg => "1")


This, in fact, won't work.

You need to do this:

MiddleMan.worker(:sample_worker).test_me({:arg => "1"}, true)


The second parameter (and make sure you put {} around your first set of args) is some freaky boolean to say, "no, seriously, I want a result back". Otherwise you get nil.

Bonus Tip!
When you define a method in a worker it HAS to accept arguments.
This won't work:
def test_me
end


This will:

def test_me(something = 1)
end

Friday 16 January 2009

Rails and eTags

In the never ending search for speed I've been doing a lot of stuff with 304 Conditional get's. Here is a good article about etags - which is a vital component to it!


Rails-ETags