Posts de September, 2010

[Emerson Macedo] Hospedagem gratuita NodeJS com Joyent Node Smartmachines

Thursday, September 30th, 2010

Já faz algum tempo que a Joyent está investindo em  NodeJS e pelo visto com força total. Passado 1 mês do Node Knockout,  a Joyent está disponibilizando hospedagem beta para o NodeJS. Essa liberação está acontecendo gradualmente e atualmente é necessário um token. Basicamente você solicita esse token e entra numa fila. Não demora muito e eles enviam um email pra você avisando que o token já está disponível. A partir daí basta recupera-lo e usar para criar sua instância.

Uma coisa legal também é que a Joyent criou uma API REST, onde é possível fazer tudo pela linha de comando. Vamos criar e fazer deploy de uma aplicação simples.

Criando a conta:

$ curl -k https://api.no.de/account \
    -F "email=user@domain.com" \
    -F "username=user" \
    -F "password=pass" \
    -F "password_confirmation=pass"

Adicionando uma chave ssh:

 $ curl -k -u user:pass https://api.no.de/sshkeys \
    -F "name=seunome" -F "key=@/Users/seuusuario/.ssh/id_rsa.pub"

Solicitando um coupon para provisionar uma máquina:

$ curl -k -u user:pass https://api.no.de/heart -X POST

A partir daí, você deve esperar o seu email chegar, avisando que o coupon está liberado. Como eu disse, essa liberação é gradual. Portanto, muita calma nessa hora. Talvez você tenha que voltar no artigo depois, portanto guarde o link :)

Provisionando sua máquina (quando o coupon estiver liberado):

Pegue o coupon:

$ curl -k -u user:pass https://api.no.de/coupons

Se você fizer essa busca antes do coupon estar liberado virá uma resposta vazia.

Provisione com o coupon recebido:

 $ curl -k -u user:pass https://api.no.de/smartmachines/node \
    -F "coupon=123456789abcdefghijk" \
    -F "subdomain="seusubdominio"

Nesse momento você já tem uma máquina apontando para o endereço: htttp://seusubdominio.no.de

Primeiro deploy

O deploy para uma Node Smartmachine é feito via git, assim como no heroku. Sendo assim, vamos criar um projeto simples, com um hello world e fazer o deploy:

$ mkdir seuprojeto && cd seuprojeto

Crie um arquivo chamado server.js no editor de sua preferência e adicione o seguinte código:

var http = require('http');

var server = http.createServer(function (request, response) {
  response.writeHead(200, {'Content-Type': 'text/plain'});
  response.end('Hello NodeJS Smartmachine\n');
});

server.listen(Number(process.env.PORT));

Pegue as informações (IP) da sua máquina para configurar o repositório remoto do git:

$ curl -k -u user:pass https://api.no.de/smartmachines/node

Inicialize o repositório git, faça o primeiro commit e adicione o repositório remoto:

git init
git add .
git commit -am "Primeiro commit"
git remote add joyent ssh://node@seuip/repo

Agora vamos ao que mais interessa. Deploy !!!!

git push joyent master

Pronto!!! Agora acesse htttp://seusubdominio.no.de e você deve ver a mensagem Hello NodeJS Smartmachine.

Gerênciando pela interface web

Para gerenciar sua conta visualmente basta entrar em http://no.de e entrar com seu usuário e senha. É uma ótima opção também. Nesse site também é possível criar novas contas, adicionar chaves ssh e provisionar máquinas. Não é possível

Conclusão

Essa hospedagem de NodeJS me parece bem promissora, e eu sinceramente estou gostando muito. A hospedagem permite instalar pacotes usando NPM e também instalar algumas outras coisas. No próximo artigo vou explorar detalhes de administração dessa hospedagem.

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

[Rafael Biriba] Net-sftp: Solução para timeout durante o upload

Friday, September 24th, 2010

Desenvolvendo um script em ruby para upload de arquivos, utilizando a gem net-sftp, acabei encontrando um problema. A falta de timeout durante a transferência.

Decidi escrever esse post, para compartilhar a minha solução e também receber opniões e outras soluções para o problema. Fiz algo parecido quando escrevi o post sobre o cron de 15 em 15 segundos e obtive resultados bem legais.

Introdução ao problema
A gem net-sftp é bastante utilizada em scripts ruby que requerem alguma transferência de arquivo utilizando uma conexão segura, como por exemplo, upload, download, criação de diretórios e etc… Utilizando a gem net-ssh para estabelecer essa conexão.

Primeiramente, vamor ver um exemplo do problema:

1
2
3
4
5
require 'rubygems'
require 'net/sftp'
Net::SFTP.start('192.168.0.2', 'rafaelbiriba', {:password => 'teste', :timeout => 3}) do |sftp|
  sftp.upload!("/Users/rafaelbiriba/Projects/temp/video-teste-sftp.mp4", "/home/rafaelbiriba/video-teste-sftp.mp4")
end

O código acima faz o upload de um arquivo de mais de 400 Mb. Se durante o upload houver um problema na sua conexão, o upload fica “esperando” a rede voltar, o que é um problemão, pois ele trava a execução do script, sem dar nenhum sinal de vida.

De acordo com a documentação do net-sftp, você pode utilizar o método “upload” no lugar de “upload!” para não travar a execução do script. Mas ainda sim o problema continua. Fazendo isso, você vai precisar rodar um loop até que o upload termine. Ou seja, se a conexão cair, o método só vai terminar(e sair do loop) quando a conexão voltar. Então essa opção foi logo descartada.

O problema mesmo só acontece quando há perda de conexão depois que a sessão inicia. (O parâmetro :timeout apenas estabelece o limite de espera para iniciar a sessão e não durante a transferência dos arquivos.)

Se o net-sftp / net-ssh aceitasse a opção “ServerAliveInterval” (Parametro de configuração do ssh), ele iria verificar a conexão durante a transferência e ao alcançar o limite (“ServerAliveCountMax”) seria disparado o timeout. Infelizmente, de acordo com a documentação do net-ssh esses parâmetros não estão disponíveis.

Seguindo com a leitura e busca pela solução na documentação do net-sftp, achei uma possível pista para encontrar a solução:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
require 'rubygems'
require 'net/sftp'
Net::SFTP.start('192.168.0.2', 'rafaelbiriba', {:password => 'teste', :timeout => 3}) do |sftp|
  sftp.upload!("/Users/rafaelbiriba/Projects/temp/video-teste-sftp.mp4", "/home/rafaelbiriba/video-teste-sftp.mp4") do |event, uploader, *args|
    case event
    when :open then
      # args[0] : file metadata
      puts "starting upload: #{args[0].local} -> #{args[0].remote} (#{args[0].size} bytes}"
    when :put then
      # args[0] : file metadata
      # args[1] : byte offset in remote file
      # args[2] : data being written (as string)
      puts "writing #{args[2].length} bytes to #{args[0].remote} starting at #{args[1]}"
    when :finish then
      puts "all done!"
    end
  end
end

Acima temos um exemplo simples retirado da documentação para o controle do progresso de upload.

Quando o upload inicia, o case recebe o evento “:open” onde podemos obter o tamanho total do arquivo e logo em seguida recebe os eventos “:put” onde é possível verificar  o quanto (em bytes) do arquivo já foi enviado, possibilitando assim a criação da porcentagem do upload.

Como o foco aqui não é mostrar como obter o progresso (posso mostrar isso exclusivamente em outro post), não vou entrar muito em detalhes sobre isso. O que realmente inporta nesses eventos são que durante o upload (:put), ao perder a conexão, nenhum evento é disparado, ou seja, o script não tem como sabe que a conexão caiu e a execução fica travada no upload.

Minha solução:

Depois de todo esse estudo detalhado sobre o problema, comecei a pensar numa possível solução. Eu precisava utilizar alguma coisa que pudesse verificar e dar timeout no upload caso ele parasse de responder/transferir.

Foi então que pesquisei, testei e implementei o rufus-scheduler,  um agendador de tarefas que roda dentro do script ruby(mas numa outra thread), onde posso definir um intervalo de execução para executar alguma coisa, como se fosse um cron.

Então a solução foi: Assim que o upload começar, eu defino um tempo de 10 segundos para ele executar uma tarefa com “raise exception”. Então toda vez que o upload responder ao evento (:put), informando que conseguiu subir mais um trecho do arquivo, eu reseto o tempo de execução da tarefa e defino novamente em 10 segundos. Fazendo isso, quando a conexão cair ou o upload travar por algum motivo, ele vai parar de responder ao evento “:put”, e então a tarefa agendada para 10 segundos vai disparar o raise, interrompendo a execução do script.

Depois de alguns testes, tanto de desempenho quanto de falhas, percebi que essa resolução serviu perfeitamente. Segue o exemplo do código utilizado:

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
require 'rubygems'
require 'rufus/scheduler'
require 'net/sftp'
 
@job = nil
@scheduler = Rufus::Scheduler::PlainScheduler.start_new
def @scheduler.handle_exception (job, exception)
  abort(exception.message)
end
 
def job_scheduler
  @job.unschedule unless @job.nil?
  @job = @scheduler.every "10s" do
    raise "SFTP Connection Lost or upload freezed timeout"
  end
end
 
Net::SFTP.start('192.168.0.2', 'rafaelbiriba', {:password => 'teste', :timeout => 3}) do |sftp|
  sftp.upload!("/Users/rafaelbiriba/Projects/temp/video-teste-sftp.mp4", "/home/rafaelbiriba/video-teste-sftp.mp4") do |event, uploader, *args|
    case event
    when :open then
      puts "starting upload: #{args[0].local} -> #{args[0].remote} (#{args[0].size} bytes}"
      job_scheduler
    when :put then
      puts "writing #{args[2].length} bytes to #{args[0].remote} starting at #{args[1]}"
      job_scheduler
    when :finish then
      puts "all done!"
     @job.unschedule unless @job.nil?
    end
  end
end

Quando o upload inicia (:open) e durante a transferência (:put), ele chama o método job_scheduler que é responsável por resetar a tarefa anterior(se houver) e agendar uma nova com timeout de 10 segundos.

Conforme expliquei acima, se o upload travar, o tempo limite de 10 segundos é alcançado e uma exceção é disparada.

Como o agendador do rufus roda em outra thread, qualquer exceção disparada é capturada e tratada pelo próprio rufus, fazendo com que a execução do script continue. Então, sobrescrevi o método (handle_exception) e ao receber a exceção, gero um abort que irá interromper todo o script.

Infelizmente, são poucos os sites relacionados sobre esse assunto, o que dificultou bastante a busca e o desenvolvimento. Mesmo assim, acredito ter resolvido o problema com uma solução bem razoável.

Já falei em cima, mas vale ressaltar:
“Decidi escrever esse post, para compartilhar a minha solução e também receber opniões e outras soluções para o problema. Fiz algo parecido quando escrevi o post sobre o cron de 15 em 15 segundos e obtive resultados bem legais.”

Estou pensando em tentar solucionar isso direto no código do net-sftp e quem sabe mandar um patch para o autor. De qualquer forma, fico no aguardo de qualquer comentário e/ou soluções melhores.

handle_exception

Google Bookmarks Twitter Yahoo Messenger Orkut Hotmail Google Gmail Delicious Share/Bookmark

Leia também:


[Andrews Medina] Testando tags e filtros customizados no Django

Saturday, September 18th, 2010

O sistema de templates do Django traz nativamente uma grande variedade de tags e filtros, mas, em vários casos é necessário criar tags e filtros customizados.

Criar tags e filtros está bem documentado na documentação oficial do Django, mas, como testar tags e filtros customizados é algo que gera muitas dúvidas em vários desenvolvedores.

Uma forma simples para testar filtros e tags é utilizar a própria Template API do Django para criar e renderizar uma template que utilize um filtro ou tag a ser testado.

Um teste para um filtro ‘reverse’ que inverte uma string, seria da seguinte maneira:



from django.test import TestCase

from django.template import Context, Template

class TestFilter(TestCase):

def test_lower_filter(self):

html = ‘{% load reverse %}’

html += ‘{{ username|reverse }}’

template = Template(html)

context = Context({’username’: ‘ozzy’})

assert ‘yzzo’ == template.render(context)

Assim é possivel fazer TDD no desenvolvimento de filtros e tags sem sofrimento.

[Enrico Batista] Instalando nodejs e npm no Ubuntu sem sudo

Friday, September 17th, 2010

O npm, instalador de pacotes do nodejs, recomenda que não seja instalado usando sudo, pois você acabaria instalando código de terceiros, com livre acesso ao seu sistema de arquivos. É mais fácil instalar o npm sem sudo se o próprio node também for instalado assim. Instalando o node sem sudo (fonte): # Clone do projeto [...]

[Rafael Biriba] FFmpeg: Convertendo um video em um gif animado

Friday, September 17th, 2010

FFmpeg: http://www.ffmpeg.org/

Há alguns dias atrás, me perguntaram se tinha como converter trechos de vídeos para um arquivo .gif, para ser usado em lugares que só aceitam imagens, como foruns, msn e etc…

Abaixo segue um exemplo simples que faz isso, usando FFmpeg:

ffmpeg -i teste.avi -pix_fmt rgb24 -f gif teste.gif

Detalhes:

  • -i = especifica o arquivo de entrada (teste.avi)
  • -pix_fmt = especifica o formato dos pixels (rgb24, formato suportado pelo gif  )
  • -f = força a saida para .gif
  • teste.gif é o arquivo de saída do comando

Se você precisa de uma solução mais específica, abaixo segue alguns outros comandos e as explicações dos parâmetros:

GIF com tamanho final específico e com loop:

ffmpeg -i teste.avi -pix_fmt rgb24 -r 10.0 -loop_output 0 -f gif -s 120x120 teste.gif

Detalhes:

  • -r = especifica o frame rate do gif
  • -loop_output = especifica quantas vezes o gif vai entrar em loop (zero para infinito)
  • -s = especifica o tamanho do gif em pixels

GIF com inicio definido pelo tempo e final limitado por frames:

ffmpeg -i widescreen.avi -pix_fmt rgb24 -r 10.0 -loop_output 0 -vframes 100 -ss 00:00:02 -f gif teste.gif

Detalhes:

  • -ss = especifica a posição inicial do video para gravação (formato: hh:mm:ss)
  • -vframes = limita a quantidade de frames que serão gravados a partir do video original
  • No exemplo, o gif será formado apenas pelos 100 primeiros frames depois da posição 2 segundos

GIF com inicio definido pelo tempo e final limitado pelo tempo:

ffmpeg -i widescreen.avi -pix_fmt rgb24 -r 10.0 -loop_output 0 -ss 00:00:02 -t 00:00:04 -f gif teste.gif

Detalhes:

  • -ss = especifica a posição inicial do video para gravação (formato: hh:mm:ss)
  • -t = especifica a posição final de gravação do video (formato: hh:mm:ss)
  • No exemplo, o gif será criado a partir do segundo 2 até o segundo 4 do vídeo.

Você também pode experimente a combinação dos parâmetros, até encontrar o comando que lhe sirva melhor… ;)

Google Bookmarks Twitter Yahoo Messenger Orkut Hotmail Google Gmail Delicious Share/Bookmark

Leia também:

[Enrico Batista] Desligar caps-lock automaticamente no Vim

Monday, September 13th, 2010

Estou usando o Vim mais a cada dia e existe algo que vem me incomodando há algum tempo. Para escrever trechos em maiúsculas, gosto de ativar o caps-lock e quando saio do modo de edição para o modo de comando as maiúsculas têm funções diferentes, então, por exemplo, ao invés de descer uma linha com [...]

[Rafael Biriba] Captcha no Travian: Será o fim dos scripts ?

Wednesday, September 8th, 2010

Apesar de afastado à algum tempo do jogo, resolvi escrever este post para esclarecer algumas coisas sobre a nova implementação de segurança do Travian.

Como todos já devem ter notado, principalmente a galera que está jogando com scripts no greasemonkey, a equipe de desenvolvedores do Travian adotou um novo modo de evitar os scripts… Captcha !

Mas primeiro, o que é o Captcha (wiki) ?
Captcha é um tipo de teste cognitivo mais utilizado como ferramenta anti-spam. Na prática, são testes com textos confusos, onde um computador ou um robô ou neste caso um script do travian seja incapaz de responder corretamente.

O captcha é gerado aleatóriamente pelo servidor, e você deve responder corretamente para poder continuar a navegar. Atualmente já existem meios para quebrar essa segurança por captcha, porém, não há nada desenvolvido para o travian ainda.

O problema no travian…
Alguns servidores no travian, e logo serão em todos, já possuem captcha funcionando. Existem um algorítmo rodando aleatoriamente no servidor do travian, para descobrir quem está usando scripts no jogo, e ao detectar alguém, ele habilita o captcha para essa pessoa e até algumas penalidades a mais.

Como ele descobre ?
Toda vez que você está jogando, a cada clique que você der, será uma requisição feita ao servidor do travian. Esse algorítmo verifica o intervalo de tempo com que essa requisição é feita,… Se for um intervalo curto e similar aos outros, ele acha que é um robô (script) e automaticamente habilita o captcha.

Logicamente, se você abrir muitas abas do seu navegador, e fizer muitos ataques rapidamente navegando entre as abas, é bem provável que você receba o captcha.

Punições
Como eu falei no inicio, estou meio afatado do travian e por isso não sei se o sistema de punição já está funcionando. Mas se estiver, funciona da seguinte forma:

  • Detectado pela primeira vez: 24 horas de captcha e armazens são esvaziados
  • Detectado pela segunda vez: 24 horas de captcha, armazens são esvaziados e 25% das tropas são perdidas
  • Detectado pela terceira vez: 24 horas de captcha, armazens são esvaziados e 50% das tropas são perdidas
  • Detectado pela quarta vez:  O jogador recebe banimento e o multi-hunter é avisado.

É muito importante relembrar que todas essas punições e verificações são feitas automaticamente. O algorítmo roda aleatoriamente no servidor do travian, então se você tiver usando script e ainda não foi pego, pode ter sido apenas sorte por enquanto…

Defeitos no sistema
Essa implementação ficou uma droga. Existe um video no youtube (http://www.youtube.com/watch?v=IRD8tBD3oRM) que mostra um caso onde o captcha aparece sem utilizar nenhum script. Como eu falei acima, se você for rápido no mouse, pode ser que seja punido como um robô.

E os scripts do greasemonkey ? O que fazer ?
Se o sistema de punição com o captcha estiver mesmo 100% funcionando, eu sugiro que desabilitem o greasemonkey. Até pior que isso, além de não usar mais os scripts, seria bom também evitar muitos cliques simultâneos e jogar mais “relaxadamente” até esse problema ser melhor resolvido.

E agora, o que fazer ?
Na verdade, não há muito o que fazer… Todo esse processo de detecção não depende dos multi-hunters… Eles nem podem fazer nada a respeito.
Alguns jogadores e ex-jogadores estão se mobilizando e reclamando, muito por sinal, no forum do travian. Nos foruns do mundo todo há reclamações de mau funcionamento do novo sistema. Eu gosto da idéia de reclamar no fórum. É a única coisa que podemos fazer e que está ao nosso alcance.

Concluindo…
Na minha opnião, acho muito difícil detectar um script rodando no greasemonkey… Ele simula cliques do mouse… Se você for tão rápido quanto os scripts, será detectado e punido como ele. Resta agora torcer para que eles desabilitem a ferramenta, ou então melhore a forma de detecção.

A pergunta que não quer calar: Captcha no Travian: Problema ou Solução ?
Será mesmo que a equipe conseguiu evitar os scripts finalmente ?
Espero ter conseguido esclarecer um pouco sobre o assunto… Se você tiver uma opnião, um outro ponto de vista, ou até mesmo uma experiência do jogo sobre isso, não deixe de compartilhar conosco.

Até a próxima pessoal…


Leia também:

[Tiago Motta] Solucionando IO bloqueante do mysql no ruby

Thursday, September 2nd, 2010

Fui a uma palestra muito interessante sobre performance na Oscon. A palestra No Callbacks, No Threads: Async & Cooperative Web Servers with Ruby 1.9, tratava do problema de IO bloqueante do driver do mysql para ruby, e como solução era proposto o uso de recursos como Event Machine e Fibers do ruby 1.9. Contudo, a solução não ficava nada elegante, tornando a manutenção do código muito complicada. Embora hoje haja um esforço para tornar esse trabalho transparente, consegui obter o mesmo resultado em performance basicamente aumentando o número de processos a atenderem as requisições.

Mas antes de explicar a solução é preciso demonstrar o problema. O caso é que embora tenhamos threads no ruby, alguns drivers como o do mysql são bloqueantes, ou seja, quando estão em uma operação de IO eles bloqueiam o processo inteiro, inclusive todas suas threads. Veja por exemplo o seguinte código:

class TestesController < ApplicationController  def index    Thread.new { Teste.connection.execute("insert into testes (id) select sleep(2)") }    render :text => 'ok'  endend

Teoricamente ao fazermos a requisição a este controller de teste, a requisição não deveria durar os dois segundos de espera pelo retorno do insert ao mysql. Mas não é isso que acontece. As requisições acabam sendo enfileiradas pois o processo inteiro fica bloqueado a cada execução de query no banco. Isso pode ser comprovado utilizando o Apache Benchmark.

> ab -c 10 -n 10 http://localhost:3000/testes...Concurrency Level:      10Time taken for tests:   20.514 secondsComplete requests:      10...

É bom deixar claro que o bloqueio ocorre somente ao usar o método execute do driver, que é responsável por fazer atualizações no banco. Fazendo somente consultas, o bloqueio não ocorre.

Na palestra em questão, foi demonstrado que utilizando Event Machine e Fibers é possivel desbloquear o processo utilizando callbacks. No final o tempo total foi reduzido para 2 segundos e alguns milésimos. Esse mesmo resultado eu obtive configurando um nginx com passenger configurado com 10 forks. Uma solução bem mais limpa. São apenas dois parametros, um do nginx, e outro do passenger:

worker_processes  10;#...http {    passenger_max_pool_size 10;}

E o resultado do teste:

> ab -c 10 -n 10 http://localhost/testes

Concurrency Level:      10Time taken for tests:   2.424 secondsComplete requests:      10

É claro que ainda sim a solução proposta na palestra é mais performática, até porque nela o consumo de memória é bem menor. Resta saber se essa economia vale a pena quando se pesa na balança o custo de manter um código mais complicado e os problemas que a concorrência podem trazer ao seu projeto. E para demonstrar o quão escalável é dividir as requisições em processos, fiz ainda um ultimo teste, com 1000 requisições, sendo 200 simultâneas, configurando o nginx e o passenger para trabalhar com 200 forks:

> ab -c 200 -n 1000 http://localhost/testes

Concurrency Level:      200Time taken for tests:   13.043 secondsComplete requests:      1000

Ou seja, o tempo total de teste manteve-se estável. Levando- em conta que dificilmente alguém fará um insert no banco com sleep, acredito que esssa seja uma boa solução para o problema de IO bloqueante. Ao invés de threads, utilizar processos.