Category: Technology
OS X:n sisäänrakennettu Mail on mainio sähköpostiohjelma, mutta siinä on pienet vikansa. Näistä häiritsevin on se, että lähtevien rich text -muotoisten sähköpostien leipätekstistä puuttuu oletuksena font-määre. Tämän seurauksena käy usein niin, että esimerkiksi Outlook näyttää viestien sisällön Times Roman -fontilla mutta allekirjoituksen Helveticalla. Asiaan löytyy korjaus pienen Mail-laajennuksen avulla.
UniversalMailer on Mail-sovelluksen laajennus, joka korjaa lähetettävät viestit automaattisesti järkevään muotoon. Projektin README-tiedostosta näkee, mitä kaikkea se tekee. Itse käänsin tämän laajennuksen Xcodella ja kopioin sitten UniversalMailer.mailbundle -tiedoston ~/Library/Mail/Bundles -kansioon. Toimiessaan laajennus lisää Mail-sovelluksen asetuksiin oheisen kuvan mukaisen lisävalinnan.
Laajennuksen asentamisen jälkeen kannattaa tarkastaa nämä asetukset:
- UniversalMailer-laajennuksen asetusten oletusfontti: Helvetica 12
- Mail-sovelluksen Fonts & Colors -asetuksista "Message font": Helvetica 12
- Mail-sovelluksen Signatures -asetuksista "Always match by default message font (Helvetica 12)": [x]
Kun asetukset ovat kohdallaan, viestien pitäisi lähteä ulos kauniina.
Update 23.4.2013
Jos mailbundle ei ota latautuakseen, niin seuraava asetus Terminalista ajettuna auttaa:
defaults write com.apple.mail EnableBundles -bool YES
Homebrew päivittyi hiljattain Node.js:n 0.10-versioon, mikä aiheuttaa ongelmia joidenkin moduulien kanssa. Vanhan version voi palauttaa käsipelillä checkaamalla ulos tietyn version node.rb-tiedostosta, mutta tähän on parempikin tapa Tap-toiminnallisuutta käyttäen.
Tiivistettynä uusimman 0.8-version saa pysyvästi käyttöön näin:
brew tap homebrew/versions brew uninstall node brew install node08
Tämän jälkeen voi taas huoletta ajaa brew update && brew upgrade, eikä Node enää päivity 0.10:een. Se kuitenkin pysyy uusimmassa 0.8:ssa.
Tässä pieni katsaus työkaluihin Node.js-sovellusten ajamiseen tuotantoympäristöissä. Olen kerännyt tähän muutaman relevantin moduulin tuotantoympäristöihin liittyen ja käyn läpi lyhyesti, mitä ne tekevät.
| Moduuli | Kuvaus |
|---|---|
| Supervisor | Supervisor on työkalu yksittäisen Node.js-palvelimen automaattiseen uudelleenkäynnistelyyn. Se käynnistää prosessin uudelleen, jos prosessi kuolee tai jos sen lähdekoodi päivittyy levyllä. Sopii siis myös kehityskäytön aikana siihen, ettei palvelinta tarvitse käynnistää käsin uudelleen koko ajan. |
| Always | Always tekee suunnilleen saman kuin Supervisor. Tällä projektilla on kuitenkin vähemmän aktiviteettia GitHubissa kuin Supervisorilla. |
| Cluster | Cluster on Node.js:n sisäänrakennettu moduuli, jonka avulla voi hyödyntää useampaa CPU-ydintä. Master-prosessi käynnistää palvelimesta useita rinnakkaisia worker-prosesseja, jotka voivat kuunnella esimerkiksi samaa TCP-porttia. Worker-prosessit voivat myös kommunikoida master-prosessin kanssa ja keskenään. |
| Upstart | Upstart on Ubuntun sisäänrakennettu palvelinprosessien hallintajärjestelmä. Se käynnistää bootin yhteydessä kaikki palvelimet, joilla on konfiguraatio /etc/init-hakemistossa. Lisäksi se käynnistää kaatuneen palvelimen automaattisesti uudelleen (respawn). |
| Forever | Forever hallinnoi palvelinprosesseja samaan tapaan kuin Upstart. Palvelimia käynnistetään ja pysäytetään komentamalla forever start, forever stop, forever list, jne. Omasta mielestäni Foreverille ei ole tarvetta, kun Upstart on jo käytössä. |
| Foreman | Foreman helpottaa sellaisten web-sovellusten käynnistämistä, jotka koostuvat useasta erilaisesta rinnakkaisesta palvelimesta. Procfile-tiedosto määrittelee, mitkä kaikki palvelinprosessit käynnistetään kerralla yhdellä "foreman start" -komennolla. Tämä helpottaa sovelluksen ajamista kehitysympäristössä yhdessä terminaalissa. Lisäksi Foreman integroituu Upstartiin niin, että Procfile voidaan konvertoida palvelimella Upstartin conf-tiedostoksi. Foreman on Ruby-gem ja alkujaan Rails-sovellusten ajamiseen suunniteltu. |
| Node Foreman | Node Foreman on Node.js:lle tehty versio Foremanista. Se toimii pitkälti samalla tavalla kuin alkuperäinen Ruby-versiokin, mutta ei edellytä Rubyn ja Foreman-gemin asentamista. Lisäksi se tukee joitakin Node.js:n lisäominaisuuksia, kuten package.json-konfiguraatiota ilman erillistä Procfileä. |
Mitä näistä itse käyttäisin
Paras yhdistelmä näyttäisi olevan Supervisor + Cluster + Node Foreman + Upstart:
- Supervisor huolehtii kehityksen aikana siitä, että palvelin käynnistyy uudelleen lähdekoodia muokatessa.
- Cluster hyödyntää tuotannossa useampaa CPU-ydintä.
- Node Foreman käynnistää tarvittaessa useita erilaisia palvelinprosesseja rinnakkain ja generoi Upstart-konfiguraatiot automaattisesti Procfileistä.
- Upstart pyörittää palvelimia Ubuntun käyttöjärjestelmätasolla ja käynnistää ne bootissa. Lisäksi respawn käynnistää tarvittaessa prosessit uudelleen siltä varalta, että Supervisor kaatuisi.
Jos jollain on kokemuksia tästä mallista tai paremmista ratkaisuista, niin olisi mielenkiintoista kuulla kommenteissa.
Päivitin blogini Django 1.5:een. Tässä lyhyesti tarvittavat muutokset, jotta uuden Djangon sai toimimaan vanhasta Django 1.4:stä päivittäessä. Käytössäni on MongoEngine ja tietokantoina MongoDB + Postgres.
- Django-mongoadminin päivitys korjattuun versioon. Tein tarvittavat korjaukset omaan GitHub-forkkiini.
- ALLOWED_HOSTS-asetuksen lisääminen settings.py:hyn. Ilman tätä asetusta Django ei enää toimi.
- Vanhojen {% url home %} -tyylisten template-tagien korjaaminen muotoon {% url 'home' %}.
- Vanhojen django.views.generic.simple.redirect_to -näkymien korvaaminen uusilla django.views.generic.base.RedirectView.as_view() -kutsuilla.
- Kaikkien pip-pakettien päivitys tuoreimpiin (varmuudeksi).
Näillä korjauksilla kaikki tuntuisi ainakin toistaiseksi toimivan.
Derby.js-frameworkin derby-auth-laajennuksella saa helposti lisättyä Facebook-kirjautumisen sovellukseensa. Sen ongelmana on vain, että käyttäjä ohjataan kirjautumisen jälkeen aina palvelun etusivulle. Joskus on tarvetta ohjata käyttäjä erilliselle tervetuliaissivulle. Tämä onnistuukin muokkaamalla oletusasetuksia hieman.
Facebook-asetukset
Tavallisesti derby-auth konfiguroidaan käyttämään Facebook-kirjautumista tällaisilla asetuksilla:
options = domain: 'http://localhost:3000' strategies = facebook: strategy: require('passport-facebook').Strategy conf: clientID: 'xxx' clientSecret: 'yyyyyy' callbackURL: options.domain + '/auth/facebook/custom/callback'
Viimeisellä rivillä näkyy lisäämäni callbackURL-asetus, joka määrittelee, minne Facebook ohjaa käyttäjän onnistuneen kirjautumisen jälkeen. Koska ohjaus tapahtuu Facebookin puolella, tämän URLin täytyy olla absoluuttinen eli sisältää myös domain-osoite.
Tämän jälkeen sama osoite on vielä lisättävä Derby-sovelluksen Express-reitteihin näin:
expressApp.get '/auth/facebook/custom/callback', passport.authenticate('facebook', failureRedirect: '/'), (req, res) -> res.redirect '/welcome' # success redirect
Ylläoleva koodi tarkoittaa, että autentikoinnin epäonnistuessa käyttäjä ohjataan etusivulle, ja sen onnistuessa taas /welcome -sivulle. Tästä eteenpäin /welcome voi sitten olla ihan tavallinen Derby-sivu tai mikä tahansa osoite omalla sivustolla.
Tarkkasilmäinen saattaa huomata vielä yhden ongelman. Derby-auth käyttää nimittäin omaa versiotaan passport-moduulista, jonka se konfiguroi automaattisesti. Ylläolevassa koodissa viitataan niinikään passport-moduuliin, ja viittauksen on osoitettava sen samaan instanssiin. Sen vuoksi require-kutsu täytyy tehdä näin:
passport = require 'derby-auth/node_modules/passport'
En ole varma, löytyykö tähän siistimpikin ratkaisu, mutta noin se ainakin toimii.
Alkuperäiselle sivulle palaaminen
Yllä kuvailtu ratkaisu perustuu siihen, että käyttäjä ohjataan Facebook-kirjautumisen jälkeen aina tietylle vakiosivulle. Usein olisi kuitenkin parempi palata takaisin sille sivulle, josta kirjautuminen aloitettiin. Sama pätee uloskirjautumiseen.
Käytin hetken pohtiakseni, mikä olisi tähän kaikkein derbymäisin ratkaisu. Valitettavasti kauniit ratkaisut eivät toimineet:
- Alkuperäisen sivun tallentaminen tietomalliin (model.set '_refURL'): Tämä tieto ei välity selaimelta palvelimelle.
- Alkuperäisen sivun tallentaminen sessioon (model.session.refURL): Tämä tieto ei välity palvelimelta selaimelle.
Jäljelle jää perinteinen tapa välittää alkuperäinen osoite query-parametrinä Derby-palvelimelle, tallentaa se sessioon ja ohjata käyttäjä sitten edelleen Facebook-kirjautumiseen. Se onnistuu tekemällä ensin tällainen sivupohja:
{#if _loggedIn}
<li><a x-bind="click: clickLogout" href="#" class="">Sign out</a></li>
{else}
<li><a x-bind="click: clickLogin" href="#" class="">Sign in</a></li>
{/if}
Sitten lisätään tapahtumankäsittelijät kyseisille painikkeille:
# Login click event module.exports.clickLogin = (event, element, next) -> app.history.push '/auth/facebook/custom/login?ref=' + encodeURIComponent(window.location.href) # Logout click event module.exports.clickLogout = (event, element, next) -> app.history.push '/auth/facebook/custom/logout?ref=' + encodeURIComponent(window.location.href)
Ja lopuksi vielä palvelinpuolen reitit:
expressApp.get '/auth/facebook/custom/login', (req, res) -> req.session.refURL = req.query.ref res.redirect '/auth/facebook' expressApp.get '/auth/facebook/custom/logout', (req, res) -> req.session.userId = undefined req.logout() res.redirect req.query.ref or '/' expressApp.get '/auth/facebook/custom/callback', passport.authenticate('facebook', failureRedirect: '/'), (req, res) -> res.redirect req.session.refURL or '/' # success redirect
Ylläolevan koodin toimintaperiaate on otettu derby-authin lähdekoodista. Omasta mielestäni on hiukan ongelmallista, että uloskirjautumisen yhteydessä userId vain unohdetaan kokonaan. Silloin derby-auth nimittäin luo joka kerta uuden käyttäjän MongoDB:n users-collectioniin seuraavalla sivulatauksella. Tähän pitäisi luultavasti ehdottaa jotakin parannusta.
Kokeiltuani ensin Meteoria minusta on tullut Derby.js:n suuri ystävä. Kyseessä on siis JavaScript-framework, jolla voi pienellä vaivalla koodata reaaliaikaisesti toimivia HTML5-sovelluksia. Derby-sovellus osaa käsitellä yhteistä dataa ja lähdekoodia sekä palvelimen että selaimen puolella hyvin läpinäkyvästi.
Tässä kuitenkin muutama jippo, joilla voi helposti ampua itseään jalkaan Derbyllä koodatessa, ellei ole tarkkana.
Derbyn toimintaperiaate
Derbyn perusidea on, että ensimmäisellä sivulatauksella palvelimen puolella oleva JavaScript-sovellus generoi HTML-sivun perinteiseen tapaan. Sen jälkeen käyttäjän klikkaillessa linkkejä muita sivuja ei enää haetakaan palvelimelta, vaan ne generoidaan selaimen puolella käyttäen samaa JavaScript-koodia ja sivupohjia kuin palvelimellakin.
Tämän lisäksi Derby synkronoi MongoDB-tietokantaan tallennetut tietomallit automaattisesti selaimen ja palvelimen välillä. Tietokantaan tehdyt muutokset päivittyvät heti näkyviin sivulle ilman koko sivun lataamista uudelleen. Samaten kaikkien CSS- ja JavaScript-tiedostojen muokkaaminen päivittyy reaaliajassa selaimeen heti kun uudet versiot tallennetaan palvelimelle.
Sivupohjien {{muuttujat}} ja {sidokset}
Ensimmäinen Derbystä kantapään kautta oppimani asia oli, että HTML-sivupohjissa {{nimi}} tarkoittaa eri asiaa kuin {nimi}. Tiivistettynä kyse on siitä, että {{nimi}} laajentuu vain kertaalleen kyseisen muuttujan sisällöksi. Sen sijaan {nimi} taas luo dynaamisen sidoksen, joka päivittää kyseisen muuttujan "lennossa" sivupohjaan aina sen arvon muuttuessa.
Nyrkkisääntö on, että tietomalleja käsitellessä käytetään aina {sidoksia}. Tällöin tietomallin muuttaminen päivittää uuden arvon automaattisesti sivupohjaan. Jos sivua renderöidessä taas käytetään staattisia parametrejä, jotka eivät ole tietomallin osana, sivupohjassa käytetään {{muuttujia}} niiden osalta.
Derby ei yleensä valita mitään vaikka {{muuttujat}} ja {sidokset} sekoittaisi keskenään, joten niiden kanssa on syytä olla tarkkana. Virheen huomaa lähinnä siitä, että sivun sisältö ei päivity vaikka sen pitäisi.
Piste {.sidoksen} alussa
Toinen hämäävä ominaisuus Derbyssä liittyy tietomallien luuppaamisen {#each}-rakenteella. Joku Handlebars-sivupohjiin tottunut (kuten minä) saattaisi tehdä tällaisen rakenteen:
<ul> {#each articles} <li>{title}</li> {/each} </ul>
Ylläoleva rakenne kyllä luuppaisi läpi kaikki artikkelit otsikoineen, mutta yksittäisen artikkelin otsikon muokkaaminen ei päivittyisikään listaan reaaliajassa. Derby nimittäin vaatii käyttämään pistettä sidoksen alussa, kun kyse on suhteellisesta nimipolusta:
<ul> {#each articles} <li>{.title}</li> {/each} </ul>
Tämä kesti aika pitkään huomata ja ehdin kehitellä omissa projekteissani jo erilaisia workaroundejakin siihen, ettei sisältö tahtonut päivittyä. Oikein koodattuna sivupohja kuitenkin toimii ja jokainen muutos päivittyy siihen saman tien.
Celery is a great tool for running asynchronous Django tasks, but it can be complicated to configure. One use case I often face is running multiple web applications on the same server, each with their own Celery daemon.
The web apps are typically completely unrelated and may be running different versions of Django, Celery and other packages in separate virtualenvs. For this reason, I also want to keep their Celery backends separated.
There are a quite a few ways to do it:
- Use a database server (SQL or NoSQL) as Celery's backend, and use separate databases for each app. This is often the default solution, but not the most effective one.
- Use a message queue server as Celery's backend, and use separate queues for each app. You will need to choose a queue server and install it just for this purpose.
- Use Redis as Celery's backend, and use separate Redis database numbers for each app. You will need to coordinate so that each app has a unique database number, which can be quite tiresome.
- Use Redis, and use the same database number but separate queue names for each app. Easy, effective and simple! This is also nice for local development, since you usually already have a Redis server running on your laptop and it needs no configuration.
Using Redis with separate queue names
To use a local Redis server as the Celery backend, all you need in Django's settings.py is this:
BROKER_URL = 'redis://' CELERY_DEFAULT_QUEUE = 'myapp'
Another web application would then use a different name for the queue:
BROKER_URL = 'redis://' CELERY_DEFAULT_QUEUE = 'anotherapp'
When you run the Celery daemon processes, each just needs to know which queue it's watching:
manage.py celeryd -Q myapp manage.py celeryd -Q anotherapp
How is all this stored in the Redis database? You will see a new entry appear:
1) "_kombu.binding.celery"
Under that key, which is a SET, you can see the names of all the configured queues as something like this (use the Redis SMEMBERS command):
1) "celery\x06\x16\x06\x16myapp" 2) "celery\x06\x16\x06\x16anotherapp"
Whenever a new task is created, a Redis key temporarily appears for its queue:
2) "myapp"
As the Celery daemon picks it up, the key is immediately deleted so you won't usually see it unless you stop the daemon.
Note that the queue names are used as a top level Redis keys without any prefixes, so you should choose them wisely.