10 Minuten-Railssicherheit 0

Posted by fwoeck
on Friday, June 05

Den Vortrag “Security – What rails will and won’t do for you” von Rory McCune im Rahmen von Scotland On Rails 2009 habe ich zum Anlass genommen, den überfälligen Aspekt Sicherheit in meinem laufenden Railsprojekt anzugehen. Der Sprecher benennt XSS- und SQL-Injection-Angriffe als das bei weitem gewichtigste Problem in diesem Zusammenhang.

SQL-Injection

Solange man keine ungeprüften Daten, die potentiell aus Usereingaben stammen in Sql-Statements manuell verpackt, sorgt ActiveRecord dafür, dass alles automatisch sanitized wird. Insofern ist hier Entwarnung angesagt. Konkret heißt das, wenn direktes SQL erzeugt wird, dann NICHT so:

"name='#{@user.name}' and user_id='#{@user.id}'"

sondern SO:

["name='%s' and user_id='%s'", @user.name, @user.id]

Strategien gegen Cross-Site-Scripting

Hier schlägt man uns zwei Möglichkeiten vor:

  1. Prüfung von Userdaten bei der Eingabe
  2. Prüfung bei der Ausgabe

Für beide Varianten gibt es Plugins (siehe die Weblinks). Ich habe mich für die zweite Möglichkeit entschieden und safe-erb installiert:

script/plugin install git://github.com/abedra/safe-erb

Es wirft bei jeder un-sanitizeten Ausgabe eines Strings, der aus DB- oder File-IOs stammt eine Fehlermeldung. Das macht das unbeabsichtigte Übergehen von Lücken schwierig. Die h-Helfermethode Escape-t Tags:

<%=h @user.name %>
oder
<%= h(@user.name) %>

und sorgt dafür, dass tainted Strings untainted werden, d.h. durch die Prüfung kommen.

Zwei Tücken musste ich überwinden:

a) Daten die mit dem Rails-Fragmentcaching in memcached gespeichert werden, werden automatisch tainted, d.h. als potentiell unsicher eingestuft. Das ist nicht hilfreich, weil die Fragmente natürlich Tags enthalten. J. Roller schlägt in seinem Blog folgende Lösung vor, die die Ausgaben direkt in der memcached-Anbindung untaint-et. Sie wird z.B. in einem Initializer eingetragen:

1
2
3
4
5
6
7
8
9
class ActiveSupport::Cache::MemCacheStore   
  logger.info "Installing Memcache-safe_erb Patch"
  
  def read_with_untaint(*args)
    read_without_untaint(*args).untaint
  end
  
  alias_method_chain :read, :untaint
end

b) Bei der Einbettung von Javascript-Schnipseln gab es teilweise Probleme – auch hier darf man natürlich nicht die h-Methode von Rails benutzen, die Tags entfernt, sondern muss den Ergebnisstring untainten:

<%= (observe_field "project_active_#{project.id}", ..., :method => :post).untaint %>

Weblinks

  1. www.mccune.org.uk
  2. github.com/abedra/safe-erb
  3. code.google.com/sanitizeparams
  4. code.google.com/xssterminate
  5. www.owasp.org/Category:OWASP_Top_Ten_Project
  6. www.jroller.com/../fixing_safe_erb_with_memcached

Die Google-Analytics API spricht mit dir 0

Posted by fwoeck
on Saturday, May 02

Kurz nach der Veröffentlichung der neuen Google-Analytics-API gibt es jetzt das gem garb, welches die Kommunikation damit erheblich vereinfacht. In diesem Beispiel frage ich die Werte visits, pageviews, etc. für den letzten Monat ab und übergebe sie dem jQuery-Plugin flot, das diese Daten auf eine recht hübsche interaktive Weise darstellt:

Hier folgen die Ergänzungen zu einer leeren Rails-2.3.2-Instanz. Das gesamte Projekt habe ich zum Clonen und Spielen auf Github eingestellt (s.u.).

Die Umgebung

Das gem wird aufgerufen (> 0.2), aus einem YAML-File werden die Accountinformationen eingelesen und eine Session mit Google etabliert.

In config/environment.rb:

1
2
3
4
5
6
7
8
9
10
...
Rails::Initializer.run do |config|
...
  config.gem "vigetlabs-garb", :lib => "garb"
...
end

google_path = File.join(RAILS_ROOT,"config","google.yml")
@@google_config = YAML.load(File.read(google_path))[RAILS_ENV]
Garb::Session.login(@@google_config['login'], @@google_config['password'])

Anmeldedaten

Das File config/google.yml enthält die Domaindaten:

1
2
3
4
5
6
7
8
9
development:
  domain: rnotes.bm.net
  login: me@bm.net
  password: p4ssw0rd

production:
  domain: rnotes.bm.net
  login: me@bm.net
  password: p4ssw0rd

Controller-Code

Wenn der Welcome-Index aufgerufen wird, wird die Datenabfrage im Controller assembliert und aufgerufen. Einer sauberen MVC-Kultur folgend, sollte man dies in einem richtigen Projekt vielleicht in ein Modell geben.

app/controllers/welcome_controller.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class WelcomeController < ApplicationController

  def index
    profile = Garb::Profile.all.select {|p| p.title == 
                              @@google_config['domain']}.first
    report = Garb::Report.new(profile, :start_date => 
                              4.weeks.ago, :end_date => Time.now )

    report.metrics :pageviews, :visits, :visitors, :new_visits
    report.dimensions :date
    report.sort :date
    result = report.results

    @visitors = result.map(&:visitors)
    @visits = result.map(&:visits)
    @newvisits = result.map(&:new_visits)
    @pageviews = result.map(&:pageviews)
    @dates = result.map {|d| d.date.to_time.to_i * 1000 }
    @domain = @@google_config['domain']
  end

end

Hier ist bemerkenswert, wie die Datumsformate “CCYYMMDD”, die von Google als String kommen in Javascript-Timestamps umgerechnet werden, wie flot sie erwartet.

Die Diagrammdarstellung

Im Index werden die Ergebnisse ein ein Javascript eingefügt, das letztlich flot benutzt. Weitere Beispiele mit verschiedenen Optionen findet man auf der Website des Plugins.

Das app/views/welcome/index.html.erb-File:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<h1><%= @domain -%></h1>

<p><br /></p>

<div id="placeholder" style="width:600px;height:300px"></div>
<div id="overview" style="margin-left:200px;margin-top:20px;
                                           width:400px;height:50px"></div>

<script id="source" language="javascript" type="text/javascript">
$(function () {
    var d1 = [<%= i = -1; @visitors.map {|v| i += 1; 
                        "[#{@dates[i]}, #{v}]" }.join(", ") -%>];
    var d2 = [<%= i = -1; @visits.map {|v| i += 1; 
                        "[#{@dates[i]}, #{v}]" }.join(", ") -%>];
    var d3 = [<%= i = -1; @pageviews.map {|v| i += 1; 
                        "[#{@dates[i]}, #{v}]" }.join(", ") -%>];
    var d4 = [<%= i = -1; @newvisits.map {|v| i += 1; 
                        "[#{@dates[i]}, #{v}]" }.join(", ") -%>];
    var d = [ { label: 'Visitors', data: d1 }, { label: 'Visits', data: d2 }, 
        { label: 'Pageviews', data: d3 },  { label: 'New visits', data: d4 } ];

    // helper for returning the weekends in a period
    function weekendAreas(axes) {
        var markings = [];
        var d = new Date(axes.xaxis.min);
        // go to the first Saturday
        d.setUTCDate(d.getUTCDate() - ((d.getUTCDay() + 1) % 7))
        d.setUTCSeconds(0);
        d.setUTCMinutes(0);
        d.setUTCHours(0);
        var i = d.getTime();
        do {
            // when we don't set yaxis the rectangle automatically
            // extends to infinity upwards and downwards
            markings.push({ xaxis: { from: i, to: i + 2 * 24 * 60 * 60 * 1000 } });
            i += 7 * 24 * 60 * 60 * 1000;
        } while (i < axes.xaxis.max);

        return markings;
    }
    
    var options = {
        xaxis: { mode: "time" },
        selection: { mode: "x" },
        legend: { position: 'sw' },
        points: { show: true },
        lines: { show: true },
        grid: { markings: weekendAreas }
    };

    var plot = $.plot($("#placeholder"), d, options);

    var overview = $.plot($("#overview"), [d1, d2, d3, d4], {
        lines: { show: true, lineWidth: 1 },
        shadowSize: 0,
        xaxis: { ticks: [], mode: "time" },
        selection: { mode: "x" }
    });

    $("#placeholder").bind("plotselected", function (event, ranges) {
        // do the zooming
        plot = $.plot($("#placeholder"), d,
                      $.extend(true, {}, options, {
                          xaxis: { min: ranges.xaxis.from, max: ranges.xaxis.to }
                      }));

        // don't fire event on the overview to prevent eternal loop
        overview.setSelection(ranges, true);
    });

    $("#overview").bind("plotselected", function (event, ranges) {
        plot.setSelection(ranges);
    });
});
</script>

Javascripts und JRails

flot selbst benötigt jQuery. Eine elegante und schnelle Art der Einbettung ist es, das jRails-plugin zu installieren – es bringt jQuery mit:

script/plugin install http://ennerchi.googlecode.com/svn/trunk/plugins/jrails

Das fügt die rechten Files in public/javascripts ein. Es fehlt nur noch das jquery.flot.js:

  1. jrails.js
  2. jquery-ui.js
  3. jquery.flot.js
  4. jquery.js

das html-Layout

Zum Schluss müssen die Scripte im Layout aktivert werden – app/views/layouts/application.html.erb:

1
2
3
4
...
  <%= javascript_include_tag :defaults %>
  <%= javascript_include_tag 'jquery.flot' %>
...

Weblinks

  1. github.com/fwoeck/analytics
  2. introducing-garb-access-the-google-analytics-data-export-api-with-ruby
  3. github.com/vigetlabs/garb
  4. code.google.com/p/flot
  5. code.google.com/gdataReferenceDimensionsMetrics

Sehr cooles Debug- und Analysetool: rack-bug 0

Posted by fwoeck
on Wednesday, April 29

Die Rackifizierung greift um sich! Hier sehen wir eine Folge: Bryan Helmkamp hat grade sein an ein Django-Tool angelehntes rack-bug vorgestellt. Es ist eine Rackmiddleware, die ein Statusfenster in die laufende Website einblendet. Es gibt eine Menge sehr interessanter Möglichkeiten, z.B. die Auswertung

  1. der verbrauchten CPU-Zeiten en Detail
  2. der Session- und Cookievariablen
  3. der ENV- und Headerumgebung
  4. der SQL-Statements mit Backtrace, Explain, Timings und der Wiederholung einzelner Selects
  5. der Cachinhalte

und mehr!

Die Installation

... ist trivial (hier für Rails):

script/plugin install git://github.com/brynary/rack-bug.git

Anschließend ruft man die Middleware auf. In der config/initializers/middleware.rb:

require "rack/bug" 

ActionController::Dispatcher.middleware.use Rack::Bug,
  :ip_masks   => [IPAddr.new("127.0.0.1")],
  :secret_key => "KsdUrsfdY7sdfDEsdf52sdfhD4yWY+8z1",
  :password   => "p4ssw0rd"

Nun lässt sich das Tool auf der Unterseite /__rack_bug__/bookmarklet.html des Projekts aktivieren.

Weblinks

  1. www.brynary.com/rack-bug-debugging-toolbar-in-four-minutes
  2. github.com/brynary/rack-bug

multibox - Prototyp einer Multi-User DB-Verschlüsselung 0

Posted by fwoeck
on Sunday, April 26

Grade habe ich multibox auf Github publiziert. Um mir die doppelte Pflege zu sparen, verweise ich einfach auf den Eintrag dort und belasse es bei einer kurzen Beschreibung hier:

What it is

multibox is a quick prototype rails app that provides

  • a minimalistic user management via authlogic
  • an encrypted db-file store mechanism via strongbox and attachment_fu
  • a master key-phrase that encrypts the files
  • separate passwords/key files on a per user basis

The idea is to have

  • one central key/phrase to en-/decrypt all data without giving the main password to users
  • the ability to revoke single user’s access
  • the ability to change a user’s password without changing the master pass-phrase

Weblink

  1. github.com/fwoeck/multibox

autocomplete vs. autocomplete 0

Posted by fwoeck
on Tuesday, April 21

Nachdem ich mein aktuelles Railsprojekt von Prototype auf jQuery umgesattelt hatte, war die autocomplete-Funktion für die Formularelemente gebrochen.

Also altes Plugin raus, neues rein – z. B. das von Dylan Verheul. Man muss sich die einzelnen Files ein bisschen zusammenklauben:

cd public/javascripts/
wget http://dyve.net/jquery/js/jquery.autocomplete.js

cd ../images/
wget http://dyve.net/jquery/img/indicator.gif

cd ../stylesheets/
vi ui.autocomplete.css

mit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
.ac_input {
  width: 200px;
}
.ac_results {
  padding: 0px;
  border: 1px solid WindowFrame;
  background-color: Window;
  overflow: hidden;
}

.ac_results ul {
  width: 100%;
  list-style-position: outside;
  list-style: none;
  padding: 0;
  margin: 0;
}

.ac_results iframe {
  display:none;
  display/**/:block;
  position:absolute;
  top:0;
  left:0;
  z-index:-1;
  filter:mask();
  width:3000px;
  height:3000px;
}

.ac_results li {
  margin: 0px;
  padding: 2px 5px;
  cursor: pointer;
  display: block;
  width: 100%;
  font: menu;
  font-size: 12px;
  overflow: hidden;
}
.ac_loading {
  background : url('/images/indicator.gif') right center no-repeat;
}
.ac_over {
  background-color: Highlight;
  color: HighlightText;
}

Und schließlich das alte Plugin gelöscht:

cd ../..
rm -rf vendor/plugins/auto_complete/

ein neues Setup

Die neuen Files wollen eingebettet sein. Im Head:

1
2
<%= javascript_include_tag 'jquery.autocomplete' %>
<%= stylesheet_link_tag 'ui.autocomplete' %>

und die Views

Die Formularfelder baue ich jetzt, wie unten gezeigt. Das ist ganz sicher nicht so, wie von den ursprünglichen autocomplete-Helfern gedacht, denn die answer.id wird hier ziemlich un-RESTmäßig als Parameter übergeben – aber es funktioniert für mich…:

1
2
3
4
5
6
7
<input autocomplete="off" id="answer_<%= answer.id -%>" 
      name="answer[<%= answer.id -%>]" type="text" value="<%= answer.value_str -%>" />

<script type="text/javascript">
  $("input#answer_<%= answer.id -%>").autocomplete("/projects/<%= @project.id -%>
                     /proforms/auto_complete_for_answer_id?answer=<%= answer.id -%>")
</script>

Die Verschachtelung mit projects stammt von der Route her:

1
2
3
4
map.resources :projects do |projects|
  projects.resources :proforms, :collection => 
                   { :auto_complete_for_answer_id => :get }
...

jetzt der Controller

Dies ist die Funktion im proforms-Controller – sie sucht vergleichbare Ausdrücke in meinem Answer-Modell. Wie man hier sieht, übergibt das jQuery-autocomplete-Plugin den Suchstring als q-Parameter:

1
2
3
4
5
6
7
def auto_complete_for_answer_id
  search = params[:q]
  answer_id = params[:answer].to_i
  @vals = Answer.find(answer_id).entity.footprints.map(&:value_str).select{|v| 
                                                v =~ /#{search}/i}.uniq
  render :partial => "q_string"
end

Das Partial “q_string” rendert nur eine Ascii-Liste von Wertepaaren, und das müsste eigentlich schon Alles sein:

1
2
3
<% for val in @vals.to_a -%>
  <%= val -%>|<%= val %>
<% end -%>

Weblinks

  1. dyve.net/jquery/?autocomplete
  2. www.bassistance.de/jquery-plugin-autocomplete
  3. update-on-rails-jquery-autocomplete

Tausche Prototype gegen jQuery 0

Posted by fwoeck
on Tuesday, April 21
  1. jQuery ist irgendwie unobtrusiv und schlank
  2. die Mobilplattform, mit der ich grade angefangen habe zu experimentieren benutzt jQuery
  3. für Rails gibt es jRails, wenn man will

Zeit für einen Wechsel! Ich fang’ mit dem laufenden Railsprojekt an.

Um die gewohnten Helfer in Rails und RJS weiter zu unterstützen und den Migrationsaufwand klein zu halten installiere ich jRails:

script/plugin install http://ennerchi.googlecode.com/svn/trunk/plugins/jrails

Manuell würde man jetzt die …/application.html.erb ändern:

1
2
3
<script src="/javascripts/jquery.js" type="text/javascript"></script>
<script src="/javascripts/jquery-ui.js" type="text/javascript"></script>
<script src="/javascripts/jrails.js" type="text/javascript"></script>

Falls man allerdings die übliche Zeile zum Einbetten der Skripte verwendet:


<%= javascript_include_tag :defaults, :cache => 'jquery' %>

ist es damit schon getan – anstelle der Prototype-Files werden nun die neuen geladen. Das

:cache => 'jquery'

veranlasst Rails in der Produktion die Skripte zu einer einzigen .js-Datei zusammenzufassen, um http-Requests zu sparen.

Aus unklaren Gründen funktioniert die Sache mit dem cache… in der Produktivumgebung gut, allerdings führt sie in meiner Selenium-Testumgebung zu Problemen. Gelegentlich tauchen solche Fehler auf:

$("#project_active_1").delayedObserver is not a function

Timingproblem? Deshalb deaktiviere ich das Caching erstmal wieder.

Einiges anders

Hier folgen ein paar Änderungen, die ich anbringen musste, um die Tests zu passieren:

Alle Helferaufrufe benötigen nun ein # für ids im Selektor – die waren vorher nicht nötig:


<%= link_to_function "Person anlegen", "$('#personselect').toggle();" %>

.up() wird .parent() oder .parents():

1
2
3
4
<div class="delme">
  <p>
    <%= link_to_function "<img alt='Bild' src='delete.png' />", 
        "$(this).up('.delme').remove()" %>
wird:
1
2
3
4
<div class="delme">
  <p>
    <%= link_to_function "<img alt='Bild' src='delete.png' />", 
        "$(this).parents('.delme').remove()" %>

.value= wird .val()


$('#answer_#{answer.id}_blank_value_date').value = '1';
wird:

$('#answer_#{answer.id}_blank_value_date').val('1');

Der Autocompleter streikt

Der autocompleter ist ja schon vor längerer Zeit in ein Plugin ausgegliedert worden und mein Viewcode

1
2
3
4
5
6
7
8
<script type="text/javascript">
  //<![CDATA[ 
   var answer_<%= answer.id -%>_auto_completer = new Ajax.Autocompleter(
    'answer_<%= answer.id -%>', 'answer_<%= answer.id -%>_auto_complete', 
    '/projects/<%= @project.id -%>/proforms/auto_complete_for_answer_id', 
    {method:'get'}) 
  //]]>
</script>

erzeugt eine Fehlermeldung “Ajax is not defined” im Firebug. Arghh, na ich denke, das wird eigener Post.

Weblinks

  1. ennerchi.com/projects/jrails
  2. dev.jqueryui.com/browser/tags/latest

Ein Rails-Plugin strippt AR-Attribute 0

Posted by fwoeck
on Thursday, April 09

Das Plugin strip-attributes entfernt Whitespaces vor und nach ActiveRecord-Einträgen. Perse leere Attribute werden ge-nil-t. Dadurch entfällt die lästige Prüfung und das manuelle Strippen. Achtung: das Plugin funktioniert über einen before_validation-Hook, d.h. es werden Attribute behandelt, bevor sie gespeichert werden – nicht nachdem sie gelesen wurden!

Die Aktivierung ist schnell getan:

script/plugin install git://github.com/rmm5t/strip_attributes.git

und für jedes Modell, das man behandeln möchte braucht es etwas wie:

1
2
3
4
5
6
7
8
9
10
11
class DrunkPokerPlayer < ActiveRecord::Base
  strip_attributes!
end

class SoberPokerPlayer < ActiveRecord::Base
  strip_attributes! :except => :boxers
end

class ConservativePokerPlayer < ActiveRecord::Base
  strip_attributes! :only => [:shoe, :sock, :glove]
end

Wie gesagt, der Strip geschieht vor der Validierung. Wenn man gelesene Attribute behandeln möchte, kann man sich damit behelfen, .valid? an dem Datensatz auszuführen:

>> u = User.find_by_firstname('Barbara ')
=> #<User oid: "1239070455218_JUser", isemployee: nil, login: ...

>> u.firstname
"Barbara " 

>> u.valid?
=> true

>> u.firstname
=> "Barbara"

Weblinks

github.com/rmm5t/strip_attributes

"scrooge" lässt SELECTs abnehmen 0

Posted by fwoeck
on Tuesday, March 17

scrooge optimiert SELECT-Statements dadurch, dass es nach einer adaptiven Phase nur solche Attribute aus der Datrenbank holt, die später auch wirklich benötigt werden.

Dazu wird ein transparenter Proxy vor das AR-Modell geschoben, und es sind keine Anpassungen des Quellcodes nötig.

Die Installation wollte anfänglich nicht so recht mit den vorhandenen Plugins meines laufenden Projekts harmonieren – nachdem jetzt allerdings ein Rewrite (von scrooge) stattgefunden hat, klappt’s.

Das Plugin wird nur installiert, bzw. als gem eingebunden:


config.gem 'methodmissing-scrooge', :lib => 'scrooge'

und läuft. Ein konkreter Nachweis anhand von Benchmarks oder Logauswertungen steht hier noch aus.

Jedenfalls trifft die Beschleunigung für diese Fälle in Kraft:

  1. Select-Statements, die
  2. keine JOINS enthalten aber
  3. einen Primärschlüssel besitzen

Asynchroner DB-Zugriff mit mysqlplus

Neverblock hat u.a. einen Mysql-Treiber herausgebracht, der threaded und asynchron arbeitet. Für vielbesuchte Seiten dürfte das eine merkliche Entlastung bringen. Die Installation:

gem install oldmoe-mysqlplus

Danach ruft man den Treiber direkt als ersten in der environment.rb auf – er ersetzt die nativen Rails-query-Aufrufe durch async-query.


require 'mysqlplus'

Weblinks:

10x schnellerer Memcache mit libmemcached 0

Posted by fwoeck
on Tuesday, March 17

Seit einiger Zeit gibt es eine in C geschriebene Memcached-Library, die deutlich schneller funktionieren soll, als z. B. die native Anbindung in Rails. Da bei der Migration nach Rails 2.3 ohnehin ein paar Fehler bei der Standardanbindung aufgetreten sind, war das ein guter Zeitpunkt für mich, die Alternative zu testen.

Leider ließen sich die aktuellen Versionen (März 2009) nicht zusammen kompilieren, und so musste ich auf etwas ältere zurückgreifen, die nun aber fluppen. Benötigt werden die libmemcached, das libmemcached_store-Plugin, das den Defaultmechanismus in Rails ersetzt und etwas Konfiguration:

Setup

wget http://blog.evanweaver.com/files/libmemcached-0.25.14.tar.gz

tar -xzvf libmemcached-0.25.14.tar.gz

cd libmemcached-0.25.14

./configure && make && make install

wget http://blog.evanweaver.com/files/memcached-0.13.gem

gem install memcached-0.13.gem

Das Plugin:

script/plugin install git://github.com/cheald/libmemcached_store.git

Konfiguration

Zumindest muss das Plugin in der environment.rb aktiviert werden:


config.cache_store = :libmemcached_store

Wer es etwas ausgefallener möchte, kann für Sessions und Fragmente separate Server ansprechen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GENERAL_CACHE_SERVERS = ["localhost:11211"]
GENERAL_CACHE_OPTIONS = {:untaint => true}
SESSION_CACHE_SERVERS = ["localhost:11211"]
SESSION_CACHE_OPTIONS = { :prefix_key => "session:marsxpress" }
SESSION_MEMCACHE_CLIENT = Memcached.new(SESSION_CACHE_SERVERS, 
    SESSION_CACHE_OPTIONS)

config.cache_store = :libmemcached_store, GENERAL_CACHE_SERVERS, 
    GENERAL_CACHE_OPTIONS
config.action_controller.session_store = :libmemcached_store
config.action_controller.session = {
    :cache => SESSION_MEMCACHE_CLIENT,
      :expires_after => 86400
}

andere Funktionen

Das libmemcached-Paket bringt einige Tools mit, mit deren Hilfe man sich die Betriebstemperatur seiner Cachingserver ansehen kann:

  • memcat – Copy the value of a key to standard output
  • memflush – Flush the contents of your servers
  • memrm – Remove a key(s) from the serrver
  • memcp – Copy files to a memached server
  • memstat – Dump the stats of your servers to standard output
  • memslap – Generate testing loads on a memcached cluster

Weblinks

  1. github.com/cheald/libmemcached_store
  2. tangent.org/552/libmemcached.html
  3. blog.evanweaver.com

den lahmen Rails-Dev-Mode beschleunigen 0

Posted by fwoeck
on Wednesday, February 18

Im Entwicklungsmodus lädt Rails bei jedem Aufruf erhebliche Teile des Projekts neu, was umso schwerer ins Gewicht fällt, je größer die Projekte werden.

Das Plugin rails_dev_boost verspricht, diesen Prozess um das 10fache zu beschleunigen. Die Installation ist einfach:

script/plugin install git://github.com/thedarkone/rails-dev-boost

und die ersten Tests sehen vielversprechend aus! Unterstützt werden Rails 2.2 und 2.3.

  • Weblink:

rails-dev-boost

Konfigurationsdaten aus YAML-Files laden 0

Posted by fwoeck
on Wednesday, November 05

.. mehr Text!

config.yml:

---
from:
  jid: bot@passenger.int.bm.net/Office
  password: geheim
to:
  jid: me@passenger.int.bm.net
vcard:
  nickname: XMPP Bot
  fn: XMPP Bot
  url: http://passenger
  photo_path: avatar-red.png
require 'yaml'

config   = YAML.load_file('config.yml')
username = config['from']['jid']
password = config['from']['password']

FIXME

Starling/Workling-Combo mit god überwachen 0

Posted by fwoeck
on Wednesday, October 15

Relativ einfach lassen sich Dienste (hier Starling, Workling) mit god auf ihre Lebenszeichen, CPU- und Speicherverbrauch hin überwachen und ggf. neu starten. Man benötigt das god-gem:

gem install god

Außerdem sollte man das genuine Monitoring von Workling deaktivieren.

script/workling_starling_client:

...
:monitor    => false
...

und man erzeugt die .god-Konfigurationsdatei. Hier ein Beispiel für eine Produktivumgebung.

monitorpro.god:

RAILS_ROOT = File.dirname(File.dirname(__FILE__))

def generic_monitoring(w, options = {})
  w.start_if do |start|
    start.condition(:process_running) do |c|
      c.interval = 10.seconds
      c.running = false
    end
  end

  w.restart_if do |restart|
    restart.condition(:memory_usage) do |c|
      c.above = options[:memory_limit]
      c.times = [3, 5] # 3 out of 5 intervals
    end

    restart.condition(:cpu_usage) do |c|
      c.above = options[:cpu_limit]
      c.times = 5
    end
  end

  w.lifecycle do |on|
    on.condition(:flapping) do |c|
      c.to_state = [:start, :restart]
      c.times = 5
      c.within = 5.minute
      c.transition = :unmonitored
      c.retry_in = 10.minutes
      c.retry_times = 5
      c.retry_within = 2.hours
    end
  end
end

God.watch do |w|
  script = "cd #{RAILS_ROOT}; RAILS_ENV=production script/workling_starling_client" 
  w.name = "teaser-workling" 
  w.group = "teaser" 
  w.interval = 60.seconds
  w.start = "#{script} start" 
  w.restart = "#{script} restart" 
  w.stop = "#{script} stop" 
  w.start_grace = 20.seconds
  w.restart_grace = 20.seconds
  w.pid_file = "#{RAILS_ROOT}/log/workling.pid" 

  w.behavior(:clean_pid_file)

  generic_monitoring(w, :cpu_limit => 80.percent, :memory_limit => 100.megabytes)
end

God.watch do |w|
  w.name = "teaser-starling" 
  w.group = "teaser" 
  w.interval = 60.seconds
  w.start = "/opt/ruby-enterprise/bin/starling -p 15151 -d 
      -P #{RAILS_ROOT}/log/starling.pid -q #{RAILS_ROOT}/log/" 
  w.stop = "kill `cat #{RAILS_ROOT}/log/starling.pid`" 
  w.start_grace = 10.seconds
  w.restart_grace = 10.seconds
  w.pid_file = "#{RAILS_ROOT}/log/starling.pid" 

  w.behavior(:clean_pid_file)

  generic_monitoring(w, :cpu_limit => 30.percent, :memory_limit => 20.megabytes)
end

Das Ganze wird dann via

su - -c "god -c /var/www/techteaser/config/monitorpro.god" www-data

gestartet – z.B. in /etc/rc.local. Für die Development-Version kann man ein entsprechendes File “monitordev.god” anlegen, in dem man die “RAILS_ENV”-Variable ersetzt und den Starlingport auf 22122 einstellt.

Einige god-Abfragebasics:

> god status

> god log teaser-workling

> god terminate

Anhang zu einem Modell zufügen: attachment_fu 0

Posted by fwoeck
on Sunday, October 12

Für die Images benötigt man ein Modell (EImage), in dem die grundlegenden Eigenschaften des Plugins konfiguriert werden und für das es eine Migration gibt.

Es wird über einen Fremdschlüssel (e_image_id) in das Modell eingebettet, dem man die Anhänge zufügen möchte (Event).

Hier ist der Ablageort an das Deployment mit Capistrano angepasst: er liegt in public/system/. Dieser Pfad muss ggf. erzeugt werden.

Um zu vermeiden, dass bei einer Änderung des Event-Records das Attachment jedesmal neu geladen werden muss und damit eine verwaiste Datei in der Fileablage entsteht, ist das Uploadfeld dynamisch über einen Ajax-Request eingebunden. FIXME: hierfür gibt es bestimmt eine elegantere Lösung.

Das Modell e_image.rb:

class EImage < ActiveRecord::Base

  has_attachment :content_type => :image, 
    :max_size => 1.megabyte, :thumbnails => { :tag => "64x64 >" }, :storage => :file_system,
    :path_prefix => "public/system/#{table_name}" 

  validates_as_attachment

  has_one :event

end

Das Modell event.rb:

class Event < ActiveRecord::Base

  belongs_to :e_image
  ...

Das Partial views/events/_picture.html.erb:

<% fields_for :e_image do |img| %>
  <p>
    <b>Image</b>
    <%= img.file_field :uploaded_data %>
  </p>
<% end %>

Das Formpartial views/events/_form.html.erb: (FIXME: dies besser in RJS formulieren, dann entfällt der respond_to-Teil im Controller)

<% form_for @event, :html => { :multipart => true } do |f| %>

  <p id='picture'>
    <%= link_to_remote 'Bild ändern', :url => new_event_path, :update => 'picture', 
        :method => :get %>
  </p>
  ...

Der Events-Controller events_controller.rb:

def new
  @event = Event.new
  respond_to do |wants|
    wants.html
    wants.js { render :partial => 'picture' }
  end
end

def create
  @event = Event.new(params[:event])
  @event.e_image = EImage.new(params[:e_image])
  if @event.save
  ...

def update
  ...
  @event.e_image = EImage.new(params[:e_image]) unless params[:e_image].blank?
  ...

Und das Migrationsskript db/migrate/..._create_e_images.rb:

class CreateEImages < ActiveRecord::Migration
  def self.up
    create_table :e_images do |t|
      t.string :filename
      t.string :content_type
      t.integer :size
      t.integer :height
      t.integer :width
      t.integer :parent_id
      t.integer :thumbnail
      t.timestamps
    end
    add_column :events, :e_image_id, :integer
  end

  def self.down
    drop_table :e_images
    remove_column :events, :e_image_id
  end
end

Hier ein Teil der index.html.erb:

<div id="image">
  <p>
    <%= image_tag(@event.e_image.public_filename) if @event.e_image %>
  </p>
</div>

Schließlich etwas Konfiguration:

> mkdir -p  public/system/e_images

Starling & Workling für Hintergrundmailer 0

Posted by fwoeck
on Saturday, October 11

Mit der Starling/Workling-Kombination lassen sich elegant asynchrone verteilte Verarbeitungsprozesse realisieren. Dabei übernimmt Starling die Funktion des memCache-sprechenden Messagingservers und der Workling-Client die des abarbeitenden Prozesses. Beide können auf jeweils unterschiedlichen Netzwerkmaschinen installiert sein.

Wichtig: Soweit ich es sehe, ist es nicht möglich, Aktionen zu bestimmten Zeitpunkten zu planen (wie unter daemons), da der workling vermittelt über den Starling-Messagingserver das Ausführungssignal unmittelbar an den Workling-Client überträgt. Dieser ist zwar auch als Dienst aufgesetzt, wartet aber nur auf Meldungen aus dem Starling und kann sich nicht nach Bedraf zu anderer Zeit reaktivieren.

Installation

# gem install starling

> script/plugin install http://svn.playtype.net/plugins/workling

# /usr/sbin/adduser --shell /sbin/nologin starling
# mkdir -p /var/spool/starling
# mkdir -p /var/run/starling
# chown starling:starling /var/spool/starling
# chown starling:starling /var/run/starling

Konfiguration

environment.rb:

Rails::Initializer.run do |config|
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
    :address        => 'mail.bm.net',
    :port           => 25,
    :domain         => 'teaser.bm.net',
    # :authentication => :login,
    # :user_name      => 'n.n.',
    # :password       => 'n.n.'
  }
end

Workling::Remote.dispatcher = Workling::Remote::Runners::StarlingRunner.new

apps/workers/mailings_worker.rb:

class MailingsWorker < Workling::Base
  def send_mailing(options)
    mailing = Mailing.find(options[:mailing_id])
    mailing.deliver
  end
end

mailings_controller.rb:

class MailingsController < ApplicationController
  def deliver
    MailingsWorker.async_send_mailing(:mailing_id => params[:id])
    flash[:notice] = "Delivering mailing" 
    redirect_to mailings_url
  end
  ...

Hinweis: das Hash :mailing_id => params[:id] ist wichtig, den dies wird serialisiert von Starling übertragen und daraus bezieht der Workling-Client sein Arbeitsmodell.

mailing.rb:

class Mailing < ActiveRecord::Base
  def deliver
    UserMailer.deliver_guest_confirmation(self)
    update_attribute(:delivered_at, Time.now) 
  end
end

user_mailer.rb:

def guest_confirmation(mailing)
  @recipients   = "#{mailing.email}" 
  @from         = "teaser@bm.net" 
  @subject      = "Der nächste techteaser" 
  @body[:mailing] = mailing
end

views/user_mailer/guest_confirmation.html.erb:

Hallo <%= @mailing.name %>,
vielen Dank für Dein Interesse am techteaser!

Dienststart

> /opt/ruby-enterprise/bin/starling -p 15151 -u starling -g starling 
            -P /var/run/starling/starling.pid -q /var/spool/starling -d

> RAILS_ENV=production script/workling_starling_client start

Der Port 15151 gilt für Produktionssysteme – auf Entwicklungsumgebungen wird Starling mit ‘-p 22122’ gestartet.

Achtung: Beim Workling-Stop/Start muss das Arbeitsverzeichnis das Rails-Root sein, sonst klappt es nicht.

Weblinks

the complete guide to setting up starling

Capistrano und attachment_fu 0

Posted by fwoeck
on Tuesday, October 07

Der schnellste Weg, die Filebase von attachment_fu über ein Deployment von Capistrano hinweg zu retten, besteht darin, das path_prefix von attachment_fu zu definieren:

class MImage < ActiveRecord::Base

  has_attachment :content_type => :image, 
    :max_size => 1.megabyte, :thumbnails => { :tag => "64x64 >" }, :storage => :file_system,
    :path_prefix => "public/system/#{table_name}" 

  validates_as_attachment

  has_one :martian

end

Capistrano legt im public-Verzeichnis automatisch einen symbolischen Link namens system an, der auf ../../../shared/system verweist. Dort abgelegter Inhalt wird bei neuen Deployments also erhalten.

Es bleibt, im aktuellen Abreitsverzeichnis eventuell das system-Verzeichnis aus der Versionierung auszuschließen:

me@dev71:/var/www/bmwanted/public$ svn ps svn:ignore system .

me@dev71:/var/www/bmwanted$ echo "public/system/**/*" >> .gitignore