Ruby WEBrick アクセスログフォーマットの変更

require 'webrick'

config = {
  :DocumentRoot => '.',
  :AccessLog => [
    [ $stderr, WEBrick::AccessLog::COMBINED_LOG_FORMAT ],
  ],
}
server = WEBrick::HTTPServer.new(config)
trap("INT") { server.shutdown } # Ctrl + C で shutdown
server.start

Ruby はてなブックマーク Atom API WSSE認証

はてなブックマークAtomAPI/はてなフォトライフAtomAPI で使用するWSSE認証のHTTP X-WSSEヘッダを作成するRubyスクリプト

$KCODE = 'u'

require 'time'
require 'digest/sha1'

def hatena_wsse_header(hatena_id, password)
  # Nonce : HTTPリクエスト毎に生成したセキュリティ・トークン
  # ランダムなバイト列 http://sheepman.parfait.ne.jp/20050104.html
  nonce = Array.new(10){ rand(0x100000000) }.pack('I*')
  nonce_base64 = [nonce].pack("m").chomp # Base64エンコード

  # Created : Nonceが作成された日時をISO-8601表記で記述したもの
  now = Time.now.utc.iso8601

  # PasswordDigest : Nonce, Created, パスワードを文字列連結しSHA1アルゴリズムでダイジェスト化して生成された文字列を、Base64エンコードした文字列
  digest = [Digest::SHA1.digest(nonce + now + password)].pack("m").chomp

  credentials = sprintf(%Q<UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s">,
                        hatena_id, digest, nonce_base64, now)
  { 'X-WSSE' => credentials }
end

Ruby はてなブックマークの全ブックマークを取得するスクリプト

はてなブックマークAtomAPI FeedURIを使用して全ブックマークを取得するRubyスクリプト
require 'hatena_wsse_header' は d:id:toward:20051225:ruby_hatena_wsse

#!/usr/local/bin/ruby -w

$KCODE = 'u'

require 'net/http'
Net::HTTP.version_1_2
require 'rexml/document'
require 'pstore'
require 'hatena_wsse_header' # はてなブックマーク/はてなフォトライフ AtomAPI WSSE認証 HTTP X-WSSEヘッダを作成


hatena_id = 'hatena_id' # はてなID
password  = 'password'  # パスワード


bookmarks = [] # 全ブックマーク

# 全ブックマークを取得
Net::HTTP.start('b.hatena.ne.jp') do |http|
  offset = 0
  has_next = true
  while has_next
    header = { 'User-Agent' => "Ruby/#{VERSION}" }.update(hatena_wsse_header(hatena_id, password))
    response = http.get('/atom/feed?of=' + offset.to_s, header)
    root = REXML::Document.new(response.body).elements['feed']
    
    total_results  = root.elements['openSearch:totalResults'].text.to_i
    start_index    = root.elements['openSearch:startIndex'].text.to_i
    items_per_page = root.elements['openSearch:itemsPerPage'].text.to_i
    if total_results >= start_index + items_per_page
      offset += items_per_page
    else
      has_next = false
    end
    
    root.elements.each('entry') do |elem|
      bookmark = {}
      bookmark[:title]     = elem.elements['title'].text
      bookmark[:url]       = elem.elements['link[@rel="related"]'].attributes['href']
      bookmark[:hb_url]    = elem.elements['link[@rel="alternate"]'].attributes['href']
      bookmark[:atom_edit] = elem.elements['link[@rel="service.edit"]'].attributes['href']
      bookmark[:eid]       = bookmark[:atom_edit].split('/')[-1]
      bookmark[:issued]    = elem.elements['issued'].text
      bookmark[:summary]   = elem.elements['summary'].text
      bookmark[:tag]       = []
      elem.elements.each('dc:subject') do |tag|
        bookmark[:tag] << tag.text
      end
      bookmarks << bookmark
    end
  end
end


# 全ブックマークをPStoreで保存
db = PStore.new("hatena_bookmark-#{hatena_id}.pstore")
db.transaction do
  db['bookmarks'] = bookmarks
end


# PStoreファイルから全ブックマークを読み込み
bookmarks2 = nil
db2 = PStore.new("hatena_bookmark-#{hatena_id}.pstore")
db2.transaction do
  bookmarks2 = db2['bookmarks']
end
require 'pp'
pp bookmarks2.collect { |i| i[:title] }

ブックマークを公開しているはてなIDは、Atomフィードで同様のことが可能(WSSE認証不要)
23・24行目を変更

    header = { 'User-Agent' => "Ruby/#{VERSION}" }
    response = http.get("/#{hatena_id}/atomfeed?of=" + offset.to_s, header)

Ruby XMLRPC はてなブックマーク件数取得API

Ruby XMLRPC4R(標準添付ライブラリ)を使用したはてなブックマーク件数取得APIのサンプル

XMLRPC::Client#call - 例外が発生するバージョン

#!/usr/local/bin/ruby -w
require 'xmlrpc/client'

end_point = 'http://b.hatena.ne.jp/xmlrpc'

urls = [
  'http://www.rubyonrails.org/',
  'http://www.rubyonrails.com/',
  'http://rubyonrails.org/',
  'http://rubyonrails.com/']

client = XMLRPC::Client.new2(end_point)
begin
  result = client.call('bookmark.getCount', *urls)

  result.each do |url, count|
    puts "#{url} : #{count}"
  end
rescue XMLRPC::FaultException => e
  puts "Error: #{e.faultCode}: #{e.faultString}"
end

実行結果

http://rubyonrails.org/ : 3
http://www.rubyonrails.org/ : 87
http://rubyonrails.com/ : 1
http://www.rubyonrails.com/ : 45

XMLRPC::Client#call2 - 例外が発生しないバージョン

client = XMLRPC::Client.new2(end_point)
ok, result = client.call2('bookmark.getCount', *urls)

if ok
  result.each do |url, count|
    puts "#{url} : #{count}"
  end
else
  puts "Error: #{result.faultCode}: #{result.faultString}"
end

XMLRPC::Client#proxy - 例外が発生するバージョン

client = XMLRPC::Client.new2(end_point)
bookmark = client.proxy('bookmark')
begin
  result = bookmark.getCount(*urls)

  result.each do |url, count|
    puts "#{url} : #{count}"
  end
rescue XMLRPC::FaultException => e
  puts "Error: #{e.faultCode}: #{e.faultString}"
end

XMLRPC::Client#proxy2 - 例外が発生しないバージョン

client = XMLRPC::Client.new2(end_point)
bookmark = client.proxy2('bookmark')
ok, result = bookmark.getCount(*urls)

if ok
  result.each do |url, count|
    puts "#{url} : #{count}"
  end
else
  puts "Error: #{result.faultCode}: #{result.faultString}"
end

Agile Web Development with Rails - Chapter 6

Source Code depot1〜depot4

6.1 Iteration A1: Get Something Running

Create a Rails Application

toward@localmachine ~/rails
$ rails depot

toward@localmachine ~/rails
$ cd depot/

Configure the Application (順番入れ替え)

MySQL Administratorでデータベースdepot_developmentを作成
config/database.ymlを変更

login: &login
  adapter: mysql
  username: root
  password: xxxx
  host: LOCALHOST

development:
  database: depot_development
  <<: *login

test:
  database: depot_test
  <<: *login

production:
  database: depot_production
  <<: *login

Create the Product Table (順番入れ替え)

Migration

toward@localmachine ~/rails/depot
$ ruby script/generate migration InitialScheme
      create  db/migrate
      create  db/migrate/001_initial_scheme.rb

db/migrate/001_initial_scheme.rb 変更

class InitialScheme < ActiveRecord::Migration
  def self.up
    create_table :products do |t|
      t.column :title,       :string, :limit => 100, :null => false
      t.column :description, :text,                  :null => false
      t.column :image_url,   :string, :limit => 200, :null => false
      t.column :price,       :float,                 :null => false
    end
  end

  def self.down
    drop_table :products
  end
end
toward@localmachine ~/rails/depot
$ rake --trace migrate
(in /home/toward/rails/depot)
** Invoke migrate (first_time)
** Invoke environment (first_time)
** Execute environment
** Execute migrate

Create the Maintenance Application

toward@localmachine ~/rails/depot
$ ruby script/generate scaffold Product Admin
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/admin
      exists  test/functional/
  dependency  model
      exists    app/models/
      exists    test/unit/
      exists    test/fixtures/
      create    app/models/product.rb
      create    test/unit/product_test.rb
      create    test/fixtures/products.yml
      create  app/views/admin/_form.rhtml
      create  app/views/admin/list.rhtml
      create  app/views/admin/show.rhtml
      create  app/views/admin/new.rhtml
      create  app/views/admin/edit.rhtml
      create  app/controllers/admin_controller.rb
      create  test/functional/admin_controller_test.rb
      create  app/helpers/admin_helper.rb
      create  app/views/layouts/admin.rhtml
      create  public/stylesheets/scaffold.css

toward@localmachine ~/rails/depot
$ ruby script/server
=> Booting WEBrick...
=> Rails application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options
[2005-12-18 00:16:45] INFO  WEBrick 1.3.1
[2005-12-18 00:16:45] INFO  ruby 1.8.3 (2005-09-21) [i386-cygwin]
[2005-12-18 00:16:45] INFO  WEBrick::HTTPServer#start: pid=4008 port=3000

6.2 Iteration A2: Add a Missing Column

toward@localmachine ~/rails/depot
$ ruby script/generate migration AddProductDateAvailable
      exists  db/migrate
      create  db/migrate/002_add_product_date_available.rb

db/migrate/002_add_product_date_available.rb 変更

class AddProductDateAvailable < ActiveRecord::Migration
  def self.up
    add_column :products, :date_available, :datetime, :null => false
  end

  def self.down
    remove_column :products, :date_available
  end
end
toward@localmachine ~/rails/depot
$ rake --trace migrate
(in /home/toward/rails/depot)
** Invoke migrate (first_time)
** Invoke environment (first_time)
** Execute environment
** Execute migrate
toward@localmachine ~/rails/depot
$ ruby script/generate scaffold Product Admin
      exists  app/controllers/
      exists  app/helpers/
      exists  app/views/admin
      exists  test/functional/
  dependency  model
      exists    app/models/
      exists    test/unit/
      exists    test/fixtures/
   identical    app/models/product.rb
   identical    test/unit/product_test.rb
   identical    test/fixtures/products.yml
overwrite app/views/admin/_form.rhtml? [Ynaq] a
forcing scaffold
       force  app/views/admin/_form.rhtml
   identical  app/views/admin/list.rhtml
   identical  app/views/admin/show.rhtml
   identical  app/views/admin/new.rhtml
   identical  app/views/admin/edit.rhtml
   identical  app/controllers/admin_controller.rb
   identical  test/functional/admin_controller_test.rb
   identical  app/helpers/admin_helper.rb
   identical  app/views/layouts/admin.rhtml
   identical  public/stylesheets/scaffold.css

What We Just Did