Apr 26, 2006

RESTが難しいってことについて

暇を見つけてGoogle Data APIs Protocolをしげしげと読んでみていた。よくできているね。

Google Data APIs Protocol

ところで話はかわって、識者の人々が「RESTは難しい」「RESTを説明するのは難しい」と繰り返し発言している。事情に詳しくない(しかし興味は持っている)市井の人々に「そうなんだー、難しいんだー」という気分が蔓延するのは、REST推進派の人々にはネガティブに作用するはずで、なんでそんな発言をするのだろうと思う。私自身は門外漢なこともあって「難しい」理由がいまいちよく分からない。なんも難しくないと思えるけれど。

で、冒頭の話に戻るわけで、もしそんなに「RESTが難しい」のなら、もう「Google Data」互換APIを実現するフレームワークを作ってしまって「Operating Systemとして」使っちゃえばいいじゃんか、と思った。

どういうことかというと、RESTが難しいというのは既存のアプリケーションの実現するリソースやそのリソース上の操作があらかじめ存在していて、それをRESTっぽいリソースや操作にマップすることが難しいということだろう。あるいはRESTっぽいサービスを提供するための便利なツールキットが不足しているとか。そうだそうだ、きっとそうに違いない(笑)。

一方で、Google Data APIは任意のデータ構造のストレージとそれに対する読み書き、更新、削除と検索インタフェースを備えているので、たいていのWebアプリケーションで用いるリソースを格納する「ストレージ」への機能的要請をある程度満たしているように思える。もちろん、Google Data APIでは、並列性を確保するために更新イベントはすべてoptimisticにバージョン管理されるので、真っ当にReader-Writer Lockがないと普通のプログラムも書けん罠、とかってのはある。あってしかるべきだ。そういう場合でも上位互換APIを考えればよいだけだろう。要は、Google Data API互換フレームワーク上にWebアプリケーションを実装し、必要に応じてAPIを外部に公開すればよい。

もちろん、Webアプリケーションとしてのスケーラビリティを実現するには、Google Data APIフレームワーク自体のスケーラビリティが必要になるが、システムプログラマは単にそこに注力すればよいというoptimisticなobservationも可能だ。

…っていうのは、変な煽り記事(「Web2.0」vs「SOA」,勝つのはどっちだ?:ITpro)を読んでて思いついた与太話なので真に受けないこと。

Apr 20, 2006

Google Calendar data API

大方の予想通り、Atom Publishing ProtocolでGoogle Calendar Dataを操作できるようになった。もうどんなデータだろうが、日付情報さえあればGoogle Calendarに突っ込むことが可能だ。「Plaggerで何とか」の夢が広がりんぐ。

Google Code Blog: Google Calendar data API: time to starting coding!
Google Calendar Data API

弄っている時間がないのがとても残念。とりあえずこんな感じでPostでけた。

#!/usr/bin/perl
use strict;
use warnings;
use LWP::UserAgent;
use Encode;
use constant GAUTH_URL => 'https://www.google.com/accounts/ClientLogin';
use constant GCAL_URL => 'http://www.google.com/calendar/feeds/default/private/full';
 
my $email = 'user@gmail.com';
my $passwd = 'passwd';
 
my $entry = <<'ENTRY';
<entry xmlns='http://www.w3.org/2005/Atom'
  xmlns:gd='http://schemas.google.com/g/2005'>
  <category scheme='http://schemas.google.com/g/2005#kind'
    term='http://schemas.google.com/g/2005#event'></category>
  <title type='text'>Tennis with Beth</title>
  <content type='text'>Meet for a quick lesson.</content>
  <author>
    <name>Jo March</name>
    <email>jo@gmail.com</email>
  </author>
  <gd:transparency
    value='http://schemas.google.com/g/2005#event.opaque'>
  </gd:transparency>
  <gd:eventStatus
    value='http://schemas.google.com/g/2005#event.confirmed'>
  </gd:eventStatus>
  <gd:where valueString='Rolling Lawn Courts'></gd:where>
  <gd:when startTime='2006-04-17T15:00:00.000Z'
    endTime='2006-04-17T17:00:00.000Z'></gd:when>
</entry>
ENTRY
Encode::_utf8_off($entry);
 
my $ua = LWP::UserAgent->new();
my $auth = $ua->post(GAUTH_URL, {
    Email => $email, Passwd => $passwd, source => 'test', service => 'cl'
});
my($auth_token) = $auth->content =~ m/Auth=(.+)/;
 
my $req = HTTP::Request->new(POST => GCAL_URL);
$req->header('Authorization', 'GoogleLogin auth=' . $auth_token);
$req->content_type('application/atom+xml');
$req->content_length(length $entry);
$req->content($entry);
my $res = $ua->request($req);
print $res->status_line . "\n";
 
if ($res->is_redirect && $res->header('Location')) {
    $req->uri($res->header('Location'));
    print $ua->request($req)->status_line . "\n";
}

Google Account AuthenticationをXML::Atom::Clientでtransparentに使えるともう少し楽になるだろう。

Cybozu Office 6のカレンダーの情報を同期するだけだったら、安直には全消去、全ストアをすれば十分かな。

それとは関係ないけど、いまだにPlaggerユーザではなく、bloglines2emailを愛用している私としては、livedoor Reader用にWebService::LivedoorReaderもでっち上げたいのだが…。
→コメント欄参照のこと。

Google Groups: Google Calendar Data APIで質問して分かったこと。

上記スクリプトにあるように http://www.google.com/calendar/feeds/default/private/full というEndpoint URLを使うと、アカウントユーザのプライマリカレンダー(My Calendarsの一番上のカレンダー)に追加される。このEndpoint URL自体は、以下のURLと等価である。

http://www.google.com/calendar/feeds/XXX@gmail.com/private/full

プライマリ以外のカレンダーに追加したい場合には、Web UI経由でそのカレンダーのPrivate AddressのFeed URLを調べる。と、以下のような感じになっているはず。

http://www.google.com/calendar/feeds/XXX@group.calendar.google.com/private-magic/basic

XXX@group.calendar.google.comの部分はカレンダーIDだろう(プライマリカレンダーの場合はアカウント名がカレンダーIDとして使用されていると考えると辻褄が合う)。で、Endpoint URLには、このURLからmagic部分を除去して以下のものを使用する。

http://www.google.com/calendar/feeds/XXX@group.calendar.google.com/private/basic

Endpoint URLをUI経由でしか知ることができないのはかなりアレな感じ。そのうち改善されると思う。

Apr 15, 2006

Cybozu Office 6のカレンダーを Google Calendarで表示する

ちょっとコードを書いてみたら、Cybozu Office 6のカレンダーをGoogle Calendarで表示させることができたのでご報告。

Ogawa::Buzz: Google Calendarの後半でも述べたように、Google Calendarでは、Web上に公開してあるiCalファイルのURLをOther Calendarsに登録することで、任意のiCalファイルをインポート&表示できる。

したがって、Cybozu Office 6の外部連携用のインタフェースを(リバースエンジニアリングして)叩くか、scrapingするかしてスケジュール情報を取得し、それをiCal形式で出力してWebから参照できるようにしておきさえすれば、Google Calendarと連携できる。もちろんread-onlyだけど。

Cybozuカレンダーをscrapingするスクリプトは以下を参照。

Cybozu2ICal - ogawa - Google Code

開発版をsubversionで取得するには、コマンドラインから以下のように実行する。

$ svn co http://ogawa.googlecode.com/svn/trunk/cybozu2ical cybozu2ical

config.yamlにCybozu Office 6のURL、ログインユーザ名、パスワードを設定した上で、以下のようにコマンドラインから実行すれば、リモートにあるCybozu Office 6の公開状態のスケジュールアイテムがiCal形式で保存される。

$ ./cybozu2ical > cybozu.ics
あるいは
$ ./cybozu2ical --conf /path/to/config.yaml > cybozu.ics

こうして生成されたiCalファイルをWeb reachableなディレクトリに置こう。念のために予測されにくいファイル名にしておくことを勧める。

さて、Google Calendarでは以下の操作でこのURLをカレンダーに登録することができる。

  1. まず、左側のカレンダーリストのOther Calendarsの横の「+」をクリックする。
  2. 「Add Other Calendar」という画面が表示されるので、「Public Calendar Address」というタブをクリックする。
  3. 「Public Calendar Address」を入力するフィールドが表示されるので、iCalファイルのURLを入力して「Add」をクリックする。「Allow others to find this public calendar via Google Calendar search?」にチェックを入れておくとこのカレンダーが他のユーザの検索対象に加えられてしまうので要注意。
  4. しばらく待つと(結構時間がかかる場合もある)、追加したカレンダーの情報が左側のカレンダーリストやカレンダー本体に表示されるはず。

以上で設定は終了。あとは、cronなどでcybozu2icalを定期的に実行するようにしておけば、Google Calendarが定期的にクロールして同期を取ってくれる。ちなみにクローラのUserAgentは、「Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)」。

追記: 定期的にクロールするというのは勘違いだった。Troubleshootingのスレでも話題になっていた。追加時に一回だけクロールするというのは明らかにおかしな仕様なので中の人の対応を期待している。

Google グループ : Users - Troubleshooting

2005-04-26追記: 昨日の昼過ぎから3時間ごとにクロールされるようになっていた。テスト用に登録して動作確認後削除したicsファイルもクロールしに来ていてにんとも。

さて、奥一穂さんのWWW-CybozuOffice6というPerl Moduleがある。今のところ新着情報しかフェッチできないため、このエントリーの目的には役に立たないのだが、cybozu2ical.plの作成上大いに参考にさせていただいた。

Kazuho@Cybozu Labs: サイボウズ Office に Perl でアクセス

このモジュールは放置されているような気もするけど...、この機会に機能が充実するとよいなあ。

Apr 13, 2006

Google Calendar

Google Calendar、北ー!

Google Calendar

サイボウズなどのオンラインスケジューラを読み込んで、Google CalendarにiCal形式でポストできるPlaggerのモジュールがあればなあ。そうすればOutlookは完全に捨てられるのにね。

インタフェースは例によってよくできているのだけど、割と面白いと思ったのはカレンダーを複数持てるところ(今現在はなぜかできない…あ、でけた)。

よくあるOutlookなどのシステムでは、ただ一つのカレンダーがあり、その上に目的別に色分けされたカレンダーアイテムを配置できる。また、そのカレンダーアイテムにはそれぞれアクセス権を設定できる。これに対して、Google Calendarでは、目的別に複数のカレンダーを持つことができ、そのうちの任意のものをオーバーレイして一つのカレンダービューを生成できる。また、個々のカレンダーにそれぞれアクセス権を設定できる。さらには個々のカレンダーアイテムにも簡単な公開クラス(Public/Private)を設定できるようだ。

一概に前者が面倒で後者が楽とは断言できないけれど、仕事中には仕事のカレンダーしか更新しないし、プライベート中はプライベートのカレンダーしか更新しない、という一般的なユーザ行動を考えれば後者のアプローチに分がある気もする。

あと、Event Publisher Guideにあるように、Webなどに公開してあるスケジュール情報をカレンダーに取り込む機能も面白い。面白いのだけれど、iCal形式やMicroformatsのhCalendar形式に加えて、Google Calendar用のリンクをわざわざ作らなくてはならないのは正直どうなの、どうなのよ、と思う。iCal→gCalやhCalendar→gCalはBookmarkletやGreasemonkeyでも簡単に実現できるのできっと今週中に誰かやるだろうけれどさ。

上の方で「Plaggerでほげほげ」と書いたのは、カレンダーシステムで重要なことは、「入力の簡便さ」もあるけれど、「Syndicationの容易さ・自動化」だと考えているから。本当は外部からAtomPPでカレンダーアイテムを追加・編集・削除できるようなインタフェースがあって、Plaggerなどでそいつを叩けば済むようになると嬉しい。もちろん、AtomPPのようなActive Interfaceの代わりに、適当なURLでスケジュールをRSS/iCalでpublishしておけばGoogle Calendarが勝手にfetch & syncしてくれるというPassive Interfaceでも構わない。

追記: ちょっと調べてみたらGoogle CalendarにはWeb上に公開してあるiCalファイルのURLを指定することで、Other Calendarsの一つとして読み込めるみたいだ。てことは、サイボウズをスクレイピングしてiCalファイルを作る部分だけ何とかすればいいのか。それはもう誰かやっていそうな気がする!!

サイボウズをスクレイピングしたり、RSSなどからカレンダーアイテムっぽいものを切り出したりするのはPlaggerの仕事ね(ぉぃ)。多忙を極めているので生暖かく見守ることしかできないが…。

Apr 12, 2006

Re: Let Readers decide How RSS gets sent

404 Blog Not Found:Let Readers decide How RSS gets sent

別にORの論理に囚われてはいないだろう、と思った。

選択肢を読者の側にもたせることは当然のことである。読者が全文だけではなく要約を、日本語だけではなく英語を望むのならそれを実現したらいい。

ではしかし、極端な話をすると、視覚に障碍を持つ読者に対して、音声データに変換したcontentを持つFeedを配信するだろうか?

…しないだろう。…いやもちろんしてもいいよ、でも手元にしかるべきリーダーがあり、それを用いて音声データに変換した方が認識率の点でもスピードの点でも優れた結果が得られるし、点字ディスプレイもある。また、言語間翻訳についても同様のことが言えそうな気がする。generalなスキーマを用いた機械翻訳の結果をFeedとしてサービスされるよりは、書き手と読み手が共通に持っていると期待されるスキーマを機械翻訳に役立てた方がより良い意思疎通が可能だろうから。

もっとつまんない話をしてみようか。「本文の先頭200文字を切り出したものが『要約』」とする書き手と、「そんなものを『要約』だなんて笑止」という読み手は永遠に折り合えない、と。あるいは、読み手がひらがなのみのFeedやルビ付きのFeed、仮名と常用漢字のみを使ったFeedを求めたら、と。

つまり何が言いたいかと言えば、選択肢を読者の側にもたせることが重要なのは事実だが、それをすべて単体サーバ上で実現あるいは提供でき、かつそれが最良だと信じることには、少なくとも現時点では懐疑的にならざるを得ない、ということだ。もっと悲観的な書き方をすると、実のところ書き手は読者のことをよく理解してはいないし、機械はもっと理解していない(だから集合知なんてものに大それた期待をもってしまう、と蛇足)。

であるならば、次善の策は最もgenericな情報を、as isで、メタ情報として与えることだ。

全文を提供されていれば、その要約を生成することはそう難しくない一方、いい加減な要約から意味のある要約を生成することは困難を極める。原文(日本語文)で提供されていれば、その他言語訳を生成するのは外部サービスやFeed Reader上で既にできるかいずれはできるという期待が持てる一方、低品質な(機械翻訳された)英文訳から高品質な他言語訳を生成することは至難だ。半構造が与えられていれば、音声リーダは効率良く読み上げることができる一方、構造を除去した文章の塊を読み上げるのは甚だ効率が悪い上、あらかじめ失われた構造を再現することもまた困難だ。

だからすべての可能性を否定しないために全文を提供すると言っているのであり、これが現時点での「究極のAND解(つまりはas isで何もしない)」なのだ。残念ながら。


それとは別の話をすると、「選択論理的な何か」がしばしば作用するのは主にFeed Readerソフトウェア/サービスの問題だ。本質的には「URL」がuniqueなのであって、それに対応付けられるFeedがuniqueである必要はない。しかし、多くのソフトウェア/サービスは「FeedのURL」を登録し、その内容を閲覧するようになっている。したがって読者は「FeedのURL」のuniquenessを意識せざるを得ず、それゆえ、「どちらのFeedを読ませるか」「どちらのFeedを読むか」ということがしばしば議論の俎上に上がるのだし、Feed提供者はFeedのURLを軽々に変更することを許されないのだ。

これは明らかにおかしい、登録するのはURLにすべきだ、登録されたURLに対して複数のFeedを読者のpreferenceに従って、「取得して」もしくは「生成して」、表示すべきだというのがひとつの素朴な意見である。「取得する」部分の実装に難しい点は何もない(弾さんが指摘している通り)。

複数のFeedをremixしたFeedを配布する場合のように、remixed FeedのURL自体がそのコンテンツセットの意味を規定していると考えるのが自然なこともある。だから、Feed URLを登録するのが統一的で自然なやり方なのだという意見もあるかもしれない。しかし、そうしたmachine-generatedなFeedは、そのURL自体に関連付けられたFeedだと考えればよいのであって、本質的にFeed URLの所在を意識することを読者に強いることは一切なくなるべきだろう。

Apr 7, 2006

MT 3.2日本語版 UO Pacth (2006年4月更新分)

Movable Type 3.2日本語版Release-2が出て5か月あまり経ち、かなり枯れてきた印象がありますが...まだまだです。さて、以下のWikiページではUnofficial Patchを公開し、逐次更新しています。

MT_3_2_ja2_UO_Patch - ogawa - Google Code

前回(Ogawa::Buzz: MT 3.2日本語版 UO Pacth (12月更新分))アナウンスしてからかなりの数のパッチが溜まりましたので、以下に今年に入ってから追加した修正を示します。

一般的な修正

  • mt-db2sql.cgiを用いてデータベースの変換を行った場合に、MT::Trackbackオブジェクトが二重にコピーされる。
    Ogawa::Buzz: DBコンバート後に受信済みのトラックバックを参照できない問題への対処
    [mt-db2sql.cgi] (2006-01-14追加)
  • mt-atom.cgiが返すendpoint URLが不正な値になる。
    Ogawa::Buzz: MT 3.2のAtom API
    [lib/MT/AtomServer.pm] (2006-03-26追加)
  • mt-atom.cgiが返すAtom Feedのcontent要素にtype属性がない。また、issued要素の日付情報がRFC3338準拠でない。
    Ogawa::Buzz: Re^2: MT 3.2のAtom API
    [lib/MT/Atom.pm] (2006-03-28追加)
  • <$MTDate$>のutcオプションが無視される。
    [lib/MT/Template/ContextHandlers.pm] (2006-04-02追加)
  • トラックバックやコメントのURLが「http」以外のschemeを持つとき、不正なURLが登録される。例えば、URL欄に「https://sec.example.com/」と入力してコメントをポストすると「http://https://sec.example.com/」というURLが登録される。
    [lib/MT/Util.pm] (2006-04-03追加)
  • 月初の0時0分0秒もしくは月末の23時59分59秒に作成されたエントリーがMTCalendarで表示できない。
    [lib/MT/Template/ContextHandlers.pm] (2006-04-04追加)
  • MT::translate_templatizedで引数の未定義チェックが行われていない。このため、「Use of uninitialized value in substitution (s///)」というwarningが表示される場合がある。
    [lib/MT.pm] (2006-04-06追加)
  • 一旦保存したエントリーをプレビュー画面から再度保存すると、エントリーのベースネームが変更されてしまう場合がある。
    [lib/MT/App/CMS.pm] (2006-04-06追加)
  • mt.cfgとmt-config.cgiの両方が存在するとき、ほとんどのアプリケーションは後者を参照するが、mt-xmlrpc.cgiだけは前者を参照する。
    [lib/MT/XMLRPCServer.pm] (2006-04-07追加)
  • 各ブログの設定画面の「基本」で「変更を保存する」をクリックすると、「新規投稿」などで設定したチェックボックスの設定(更新を自動通知する先、トラックバック自動検知など)がリセットされてしまう。
    [lib/MT/App/CMS.pm] (2006-04-07追加)

性能などに関わる修正

Apr 5, 2006

MT 3.2 + SQLiteでの recently_commented_on

Movable Type 3.2-ja-2とSQLiteの組み合わせでrecently_commented_onの動作がおかしくなる現象に関して、私自身は認識しています。

MovableTypeで行こう!: MT3.2 SQLiteでrecently_commented_onがうまく働かない問題

cheebowさんが指摘されている884行目に加え、867行目も同様に修正すると、とりあえずはrecently_commented_onが正常に機能しますが、個人的にはこの修正に躊躇を覚えます。なぜなら、この修正は単なるworkaroundだからです。より本質的には、load, load_iterメソッドの引数に以下のようなsort, uniqueオプションを持つjoin clauseを与えた場合に、SQLite(およびPostgres)で必ず生じる不具合がMovable Typeには伝統的に存在し、その不具合に対して何の解決も与えないのです。

my @entries = MT::Entry->load(undef, {
    'join' => [ 'MT::Comment', 'entry_id',
                { blog_id => $blog_id },
                { 'sort' => 'created_on',
                  direction => 'descend',
                  unique => 1,
                  limit => 10 } ]
});

この不具合に起因する問題は、recently_commented_onだけでなく、mt.cgiで「コメント投稿者一覧」を表示したときにも確認できます。正常な動作では、コメント投稿者が最後にコメントした時刻の新しいものから順にリストされるべきですが、(SQLiteを利用している場合には)コメント投稿者のIDの大きいものから順にリストされてしまうはずです。

...で、少し悩んだのですが、lib/MT/Template/ContextHandlers.pmに修正を行う代わりに、以下のようにlib/MT/ObjectDriver/DBI/sqlite.pmに修正を行うことで、積年の恨みを晴らせますうまく動作します。というかするようです。あまりちゃんと確認していません。

--- lib/MT/ObjectDriver/DBI/sqlite.pm.bak	Mon Sep 19 17:46:51 2005
+++ lib/MT/ObjectDriver/DBI/sqlite.pm	Wed Apr  5 20:34:32 2006
@@ -85,6 +85,11 @@
         my $j_tbl = $j_class->datasource;
         my $j_tbl_name = 'mt_' . $j_tbl;
 
+        my $is_unique_sort = ($j_args->{unique} && $j_args->{'sort'});
+
+        ## suppress "order by" clause in a sub-select
+        $j_args->{suppress_order_by} = 1 if $is_unique_sort;
+
         $sql = "from $tbl_name, $j_tbl_name\n";
         ($w_sql, $w_terms, $w_bind) =
             $driver->build_sql($j_class, $j_terms, $j_args, $j_tbl);
@@ -103,6 +108,22 @@
         if ($o_terms && @$o_terms) {
             push @$w_terms, @$o_terms;
             push @$w_bind, @$o_bind;
+        }
+
+        if ($is_unique_sort) {
+            my $cols = join(', ', map "${tbl}_$_", @{$class->column_names});
+            my($func, $dir) = $j_args->{direction} && $j_args->{direction} eq 'descend' ?
+                ('max','desc') : ('min','asc');
+            my $s_col = "${j_tbl}_$j_args->{'sort'}";
+            my $s_sql = "from (select $cols" .
+                        ", ${func}(${s_col}) as ${func}_${s_col}\n";
+            $sql = $s_sql . $sql;
+            $w_sql = "group by $cols\n" . $w_sql;
+            $w_sql .= ") t\n";
+            $w_sql .= "order by ${func}_${s_col} $dir\n";
+
+            ## (dirty hack) suppress "distinct" keyword, if possible
+            $j_args->{unique} = 0;
         }
 
         if (my $n = $j_args->{limit}) {

チャレンジャーの皆様は是非お試しください。もしうまく動くようであれば(動かないという情報も)トラックバックやコメント欄でご連絡いただければ幸いです。

参考までに、このパッチを当てると以下のような怪しげなSQL文の代わりに、

SELECT DISTINCT entry_id, ... FROM mt_entry, mt_comment
  WHERE comment_visible = 1 and comment_blog_id = 1 and entry_id = comment_entry_id
    and entry_status = 2 and entry_blog_id = 1
  ORDER BY comment_created_on DESC
  LIMIT 10;

以下のようなSQL文が生成されます。

SELECT entry_id, ...
  FROM (
    SELECT entry_id, ..., max(comment_created_on) as max_comment_created_on
      FROM mt_entry, mt_comment
      WHERE comment_visible = 1 and comment_blog_id = 1 and entry_id = comment_entry_id
        and entry_status = 2 and entry_blog_id = 1
      GROUP BY entry_id, ...
  ) t
  ORDER BY max_comment_created_on DESC
  LIMIT 10;

For Postgres Users

ちなみに、このエントリーの述べた問題を回避するために、以前からPostgres用にはaggregate_sortオプションを追加するパッチが出回っていました。このパッチは、sortオプションの代わりにaggregate_sortオプションを使うと、ちょうど上で示したのとほぼ同等のSQLを生成するというものです。が、仮にパッチを適用したとしてもアプリケーション側の修正なしにはaggregate_sort機能は利用できませんでした。

このエントリーに掲載したパッチは、SQLite用のものですが、ほぼそのままPostgres用に転用できると思われます。チャレンジャーの皆様は、以下省略。

For MySQL Users

MySQLでは4.0くらいまでまともにsubselectをサポートしていませんでした。そのため、Movable TypeのMySQL用のコードには、subselect部分を一旦temporary tableに格納し、それに対して再度selectするように書かれています。

MySQL 4.1以降では問題なくsubselectが使えますから、SQLite用のコードをMySQL用に転用することもできるはずです。チャレンジャーの...以下略。

Apr 2, 2006

ホテル喜代治館@土肥温泉

唐突に思い立って、有給休暇をとり、西伊豆の土肥温泉に行ってきた。

Google ローカル - 34.912866,138.790358 (ホテル喜代治館)
土肥温泉 ホテル喜代治館(きよじ)・・トラベルアワード 金賞を受賞!露天風呂付き客室有り・伊勢海老お造り・鮑踊り焼き・サザエ壷焼き・ずわい蟹

楽天トラベルで妙に評判のいい西伊豆は土肥温泉の温泉旅館。外装は鄙びた海水浴場の旅館というか民宿そのものなのだが、館内は思いのほかこぎれい。

さざえ壷焼き、あわび踊り焼き、伊勢えび、ズワイガニ、舟盛り、引き上げ湯葉他のコンボで平日一泊15,000円ポッチ。食い切れないほどの海の幸の狂宴。舟盛りが二名分でこの量というのは何かが間違っている気がした。期待していなかったズワイガニも身が詰まっていて言うことなし。2時間くらいかけてゆっくり味わった。

温泉も気持ちがよかった。露天風呂から観る西伊豆の夕日が非常に美しかった。

あと仲居さんがやたら気さくに話しかけてくる。企業努力を感じるなあ。せんべい布団だけど。

ところで東京から西伊豆に行く場合、東名沼津ICから国道1号線経由136号線で南下するだけなので道に迷う心配はまったくないのだが、その国道1号線部分は立体化が不十分でやたら混雑してしまう。理想的には沼津から修善寺道路に繋がる有料道路ができればよいのだが、それが無理でも周辺の生活道路と分離してほしい。アクセス自体は悪くないのになあ。

About Me

My Photo

つくばで働く研究者

Total Pageviews

Amazon

Copyright 2012 Ogawa::Buzz | Powered by Blogger
Design by Web2feel | Blogger Template by NewBloggerThemes.com