サーバ編 |
ウェブ・サービスの世界ではREST (Representational State Transfer)というコンセプトが良く使われる。Yahoo、Google、Facebookなどのウェブ・サービスはSOAP ベースやWSDL ベースのインターフェースを非推奨あるいは不使用としており、もっぱら使いやすいRESTモデルを使ってJSONファイルによるサービスを公開している。
RESTベースでJSONファイルを交わすREST-JSONは、これまでのSOAPベースでXMLファイルを交わすSOAP-XMLに比べて、次のような利点を有する:
サイズ: SOAP-XMLに比べずっとコンパクトであり、ネットワーク上で渡すデータ量が少なく、特にスマートフォンなどのモバイル・アプリケーションにはこれが重要である。
効率: REST-JSONは構文解析が容易なので、データの抽出と変換が容易である為、クライアントのCPU負荷がずっと軽くなる。
キャッシュ: キャッシュに対応しているので、応答時間とサーバ・ロード時間が改善される。
実装: REST-JSONのインターフェイスは設計と実装がより容易である。
SOAP-XMLは一般に大量のテキストを交換するときや、銀行などのセキュアなサービスで使われている。
本章では、Dartが現時点でクライアント側およびサーバ側でRESTベースのウェブ・サービスにどのように対応しているかを紹介する。
下表は現時点で用意されているDart 2対応のライブラリの主要なものである(Dart 2非対応のライブラリが数多く存在するがいずれ対応されることが期待される):
名称 |
形態 |
用途 |
概要 |
作者 |
JsonCodec JsonEncoder JsonDecoder |
双方 |
JSONコーデック |
|
|
pub |
双方 |
コード生成パッケージ |
|
|
pub |
|
JSONシリアライザのアノテーション |
|
|
pub |
クライアント |
Googleのウェブ・サービス・アクセスの為のAPI(サンプル) |
|
|
pub |
双方 |
UriTemplate対応ライブラリ |
|
|
pub |
クライアント |
JSONP要求作成作業の簡素化 |
Matthew Franglen |
|
pub |
双方 |
json_serializableと等価 |
David Morgan |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
REST (Representational State Transfer) とは、コンピュータ科学者のRoy Thomas Fieldingが2000年に自分の博士論文の中で提唱したネットワーク・ベースのアプリケーション・ソフトウエアの指針となるアーキテクチャ・スタイルである。RESTだけだとアメリカでは公衆便所(正式にはRest Roomだろうが)になって仕舞うので、通常はこのスタイルをきちんと実装したという意味で「RESTfulな」というように、形容詞として良く使われる。
本節では、Fieldingが提案したRESTの概説と、実際のウェブ・サービスに於けるRESTfulサービスの基礎を説明する。
Roy Thomas Fieldingは1965年California生まれのアメリカ人で、California州立大学Irvine校の情報とコンピュータ科学科修士課程にいたときからウェブの標準策定に関わった。丁度このころが世界の大学や研究機関が開発の中心だったインターネットの世界でのウェブの誕生・発展期であり、非常に良い時代と環境に恵まれていたことになる。彼はこの大学で2000年に博士号を取得している。日本でも慶応大学、大阪大学、東京大学等がインターネットの日本での普及に大きく寄与したが、標準化には殆ど寄与できなかったことは反省すべきである。彼の標準化での貢献は、特に1996年のHTTP 1.0 プロトコル(RFC 1945)及び1999年のHTTP 1.1 プロトコル(RFC 2616)で大きい。HTTPはウェブの発明者ともいわれるTim Berners-Leeが考え出したプロトコルである。彼がBerners-Leeなどとともに策定したHTTPプロトコルはHTTP 1.1で強化され、現在もそのまま使われている。HTTPに関しては、筆者の「改定サーブレット・チュートリアル」の2.4節を参照のこと。彼はまたW3C (World Wide Web Consortium)でのHTMLとURI (RFC 1808, 2396)の標準化にも貢献した。彼はまたApache HTTPサーバ・プロジェクトの設立メンバのひとりであり、1999年から初代会長を3年間務めた。現在もASF (The Apache Software Foundation)の役員のひとりである。1999年にMITのTechnology Review誌の「トップ100人の35歳以下の若手革新者たち」初年度に彼が指名されている。
その彼の成果に対するご褒美としての博士論文「ネットワーク・ベースのソフトウエア・アーキテクチャのアーキテクチャ・スタイルと設計(Architectural Styles and the Design of Network-based Software Architectures)は、自分がこれまのウェブでの貢献を整理するにあたり、その基本となってきたコンセプトとしてRESTという基本原則を示して、博士論文らしくしようとした(あるいは論理武装しようとした?)ものと推察される。これはハイパーメディア上の構成要素たちが、リソースの表現(Representation)を使ってそのリソースのある状態(State)を転送(Transfer)しあうという基本コンセプトである。
このRESTという原則がウェブ・サービスの世界で注目され、その使い易さ(例えばサーバはクライアント間の状態を保持しない)からこれまでのCORBAあるいはSOAP / WSDLベースのインターフェイス設計を凌駕するほどになっている。これは上位のアプリケーション・プロトコルから下位の通信・プロトコルであるHTTPへの回帰現象ともいえよう。現在多くのウェブ・サービスのプロバイダたちが、自分のサービスの為のRESTベースのAPI(ここでいるAPIというのは、URLの一部としてサーバにパラメタを渡すWeb APIの仕様のこと)を提供している。またJCP (Java Community Process)ではJSR 311 (JAX-RS: The JavaTM API for RESTful Web Services)という仕様書が出されており、Java EE6に組み入れられている。
彼が主張しようとしていることは、彼の1998年のプレゼンテーション資料のアブストラクトにより良く示されている:
アーキテクチャはあるスタイルの実体化物(インスタンス)とも言え、たとえばWWW技術の世界ではURL, HTTP, HTML, Java applets等がこれにあたる。スタイルというのはアーキテクチャたちのなかで共通したパタンのことをいう。スタイルとしては、例えばパイプとフィルタ、リモート・セッション、イベント・ベースの統合(暗示的呼び出し)、クライアント/サーバ(明示的呼び出し)、分散オブジェクト、及び多種の分散プロセスのパラダイムなどが使われてきている。これらのスタイルの各々は要素間の通信の特定のパラダイムに最適化するように意図されている。
彼の考えでは、ウェブのアーキテクチャ・スタイルは以下の5つの基本的概念が中心になっている:
リソース
リソースの表現(representation)
表現の取得/修正の為の通信
アプリケーションの状態(state)のインスタンスとしてのウェブ「ページ」
ある状態から次の状態に移動する為のエンジン
ブラウザ(もっとも一般的に使われている)
スパイダ
なんらかのメディア・タイプのハンドラ
WWWは自分がRESTと称する新しいアーキテクチャのスタイルへと発展した」と彼はいう。このスタイルはクライアント/サーバ、パイプとフィルタ、及び分散オブジェクトのパラダイムたちの要素たちを使うことで、あるリソースの表現のネットワーク転送を最適化する。ウェブ・ベースのアプリケーションというのは、状態表現(ページ)と状態間の潜在的転移(リンク)のダイナミックなグラフだと見ることが出来る。それがもたらされる結果は、サーバの実装とクライアントのリソースの認知との分離、大きなクライアント数に十分拡張できる、無制限の規模とタイプのストリーム・データの転送ができる、データ転送とキャッシングの要素のような仲介者(プロキシとゲートウェイ)に対応できる、そしてユーザ・エージェント要素内でアプリケーションの状態に集中した、アーキテクチャであると彼はいう。
彼がRESTをどのように導き出したかは、彼の博士論文の第5章の最初の部分(5.1節)で説明されている。彼は全く空のスタイル(null style)に以下のような幾つかの制約を付加することで、RESTアーキテクチャ・スタイルを導入している:
クライアント-サーバのアーキテクチャ・スタイル:ユーザ・インターフェイスとデータ蓄積を分離することでユーザ・インターフェイスのプラットフォーム間での可搬性(portability)が高まり、またサーバが簡素化され拡張性が高まる
状態なし(ステートレス)のアーキテクチャ・スタイル:通信に状態を持たせない。各クライアントからサーバへの要求は、その要求を理解する為の総ての情報を持たせる。セッション状態はクライアント上でのみ保持される。これにより可視性(例えば監視システムはその要求を調べるだけで良い)、信頼性(部分的な障害からの復旧が簡単)、及び拡張性(サーバ側で状態情報を保持しなくても良い)が得られる
キャッシュ付加可能なスタイル:ある要求に対する応答にキャッシュできるかどうかを明示的あるいは暗示的ラベルを付加することで、効率、拡張性、ユーザが受け取る感じを改善できる
インターフェイスが統一されたスタイル:ハイパー・メディアを構成する各要素間のインターフェイスを統一する。これにより情報が標準化された形式で転送されるので、システム全体のアーキテクチャが簡素化され、また要素のインターフェイスの一般化がなされる
階層化されたシステムのアーキテクチャ・スタイル:大規模なインターネットに対応できるよう、階層化されたシステムのアーキテクチャ・パタンとする:各要素は自分が属する階層以外の階層が不可視になることで、大規模化に対応できるようになる
コード・オン・デマンド:アプレットやスクリプトなどの形式で実行コードがダウンロードできるようにする。これによりクライアント側が簡素化される。また、システムの拡張性が得られる。但しこれは可視性を阻害することになるので、RESTの中ではオプショナルな制約となっている
RESTにおけるデータ要素たちを纏めると次の表のようになる。これらはHTTPプロトコルを理解していれば理解が早い(筆者の改定サーブレット・チュートリアルの第2章参照のこと):
RESTのデータ要素 |
|
データ要素 |
ウェブでの事例 |
リソース |
ハイパーテキスト参照の意図されたコンセプト上のターゲット |
リソース識別子 |
URL、URN |
表現 |
JSON、XML、HTMLなどのドキュメント、JPEGイメージ |
表現のメタデータ |
メディア・タイプ(media type)、前回変更時刻(last-modified time) |
リソースのメタデータ |
ソースのリンク(source link)、代替(alternates)、変動(very) |
制御データ |
if-modified-since、cache-control |
リソース
リソースとはRESTにおける「情報」の重要な抽象化である。リソースは名前(識別子、実際にはURI: Uniform Resource Identifier)をもったもので、ドキュメントまたはイメージ、例えば「今日の東京の天気」といったサービス、他のリソースたちの集合、あるいは「人々」といったネットワークされていないオブジェクト、などである。URLなどで表されるリソースが指し示すのは特定のドキュメントやイメージではなく概念である、という考え方である。リソースはある実体のセットへのコンセプト的なマッピングであり、ある特定の時刻におけるそのマッピングに対応した実体のことではない。即ちリソースは時間の関数として実体のセットあるいはそれと等価な値たちを持つ。あるセットのなかの値たちは、リソース表現(resource representations)あるいは/及びリソース識別子たち(resource identifiers)ということになる。
RESTでは、要素間の係わり合いのなかで関与される特定のリソースを特定する為に、リソース識別子(resource identifier)が使われる。HTTPでいえばURIがこれに相当する。
リソースの表現
RESTの構成部品たちは、そのリソースの現在のあるいは意図した状態(state)を捕捉するのに表現(representation)を使い、またその部品(components)間でその表現を転送(transfer)することで、あるリソースにたいするアクションを行う。RESTの構成部品たちは、そのリソースに直接関わりあおうことは無く、あくまでもその表現を介すことになる。表現はバイト列、及びそれに加えてそれらのバイトを記述する為の表現メタデータ(通常は名前と値のペアたちで構成)である。彼はウェブはリソースの表現を操作し転送するよう設計されているという。例えばHTTPではヘッダ部分がメタデータ部分であり、ボディ部分はバイト列用(通常はテキスト/HTML、MIMEなどのタイプで)として使用されている:
単一のリソースは複数の表現に結び付けられていても良い(コンテント交渉)
表現のデータ・タイプはメディア・タイプとして知られているものである(例えばMIMEなど)
このリソースの情報を提供
ハイパーメディア認知のメディア・タイプをとる
潜在的な状態遷移を提供する
殆どの表現はキャッシュできる
Fieldingは自分のインターネットの世界での貢献のベースとなったコンセプトをRESTという言葉で示したが、ウェブ・サービスの世界の人たちはこのコンセプトをより自分なりにより狭く解釈している。従ってその定義はとくに標準化されている訳ではない。一般的にはFieldingの定義(「RESTの導入」の項で述べたクライアント / サーバ、ステートレス、キャッシュ対応、階層化などの制約)に対し、更に以下のものが付加されている:
HTTPベースである
RESTの導入にあたってFieldingはインターフェイスの統一という制約を加えたが、ウェブ・サービスの世界ではHTTPがインターフェイスのベースとなっている。従ってGET、POST、PUT、DELETEなどのHTTPメソッドを使ってクライアントはサーバにアクセスする。これらのメソッドは厳格に区別して使用される:
GET:取得(GET要求でリソースに何らかの影響を与える使い方はしてはならない)
POST:新規作成
PUT:更新
DELETE:削除
これらはしばしばCRUD (Create, Read, Update, Delete)操作と呼ばれる
従ってリソースの表現に対する要求はHTTPの要求行のURIからのみとなる
HTTPに関しては別途説明するが、HTTPの他のヘッダ行を使って要求先を指定してはならない。逆に言えばリソースはURIを介してのみ到達可能である(但しPOSTなどで送信されるデータはHTTPメッセージのヘッダ行とボディ部分が関与する)
表現には一般的な標準であるデータ・フォーマットを使用する
XML、HTML、Atom、RSS、JSON、CSV、GIFなど
特にJSONが多用されていて(REST-JSON方式)、RESTといえばJSONだと一般的に理解されるまでになっている。
MIMEタイプとしては以下のものが使われる:
text/xml
text/html
application/json
image/gif
image/jpeg
等々
ハイパーリンクを使ってリソース間の関連(連鎖)を伝えたり、アプリケーションの状態遷移(一連のアクションに於ける関連ステップのことで、クライアントとの間の状態遷移とは意味が異なる)をクライアントが選択する手段を伝える。状態を制御・維持するのはクライアントである
これは「連結性(Connectedness))」とも呼ばれる。あらゆるRESTベースのシステムは、クライアントが関連リソースにアクセスする必要があることを前提としており、リソース表現に関連リソースを含めてクライアントに返す。
RESTfulなウェブ・サービスでは既存の良く知られたW3CとIETFの標準たち(HTTP, XML, URI, MIME)が使われており、また最小限のツーリングでウェブ・サービスを構築できるので、RESTfulなウェブ・サービスの開発コストが低くて済み、従って導入の障壁が非常に低い。RESTfulなウェブ・サービスの開発にはEclipseのような統合開発環境が使え、開発がより簡単化される。
OracleのJava EE6チュートリアルでは、以下のような条件が満たされるときが、RESTfulなウェブ・サービスを設計するのが適正な場合であろうと書いている:
そのウェブ・サービスが完全にステートレスであるとき。そのリソースへの係わり合いが、サーバの再起動でも影響を受けないようにできるかどうかがひとつの判断基準となる。
キャッシュによってサービス性能が改善できるとき。そのウェブ・サービスが返すデータが動的に生成されたものでなくキャッシュ可能な場合は、ウェブ・サーバたちや仲介サーバたちが提供するキャッシングにより性能を改善できる。但し殆どのサーバにとってはキャッシュはHTTP GET要求のみに制限されているので、開発には注意が必要である。
サービスの提供側と消費側が互いに渡すデータのコンテキストと中身を理解しているとき。ウェブ・サービスのインターフェイスを記述する公式な方法が無い為、双方は別途交換されているデータを記述するスキーム、及びそれを有意義な形で処理する手段で同意がとられていなければならない。実際には、RESTful実装としている殆どの商用のアプリケーションでは、一般的なプログラミング言語でそのインターフェイスを記述したいわゆる付加価値ツールキットを提供している。
帯域が特に重要であって、帯域制限の必要がある場合。PDAや携帯電話機のようにプロファイル制限があって、XMLペイロード上でのSOAP要素たちのヘッダや付加的レイヤのオーバヘッドが問題となる機器にとって、RESTは特に有用である。
RESTfulスタイルにより、既存のウェブのサイトたちにウェブ・サービスのデリバリ(提供)あるいはアグレゲーション(統合)が容易になる。開発者たちはJAX-RS及びAJAX (Asynchronous JavaScript with XML)のような技術が使えるし、自分たちのウェブ・アプリケーションの中でそのサービスを使うのにDWR (Direct Web Remoting)のようなツールキットを使うことが出来る。白紙から始めるのではなく、既存のウェブ・サイトのアーキテクチャを大きく変えることなく、サービスはXMLで表現され、HTMLページで消費されるようにできる。既存の開発者たちは、新しい技術で最初から始めるのではなく、既に馴染みがあるものを付加することになるので、彼らはより生産的になる(仕事がはかどる)
Dartチームはその教材として簡単なサンプルを用意している。従ってこれを使ったクライアントとサーバのコードを試してみよう。
最初にHTML formからGET要求をする number_thinker.dart(サーバ)とmake_a_guess.html(クライアント)を試す。
教材のサンプル集のリポジトリからをZIPファイルダウンロードし、解凍する。
解凍するとDownloads\dart-tutorials-samples-master\dart-tutorials-samplesというフォルダをIDEで開く。
|
number_thinker.dartをサーバとして走らせ、make_a_guess.htmlのファイルパスをコピーしてブラウザのアドレスバーに貼り付けて開く、または localhost:8080/make_a_guess.htmlでこのファイルを呼び出す:
|
適当な数を選択してGuessボタンをクリックするとサーバが当たりかどうかを返してくる
|
このアプリではRESTに準拠している。ここでクライアントからのGET要求は以下の目的で使用される:
データの取り出しにのみ使用される
サーバの状態を変えない
長さの制限がある
URLのなかでクエリ文字列(この例では?q=1)として要求が送れる
この例ではREST準拠のGET要求がされている
ここではbasic_writer_server.dartとbasic_writer_client.dartを使用する。このアプリはあるサーバが別のサーバに対しRESTベースでデータを送受する用途を想定している。
file/txtファイルを開き、何かが残っていたらそれを消去する
最初にbasic_writer_server.dartを走らせる。
次にbasic_writer_client.dartを走らせる:
|
またfile/txtファイルには次のようなJSONテキストが残される:
|
このようにクライアントがあるJSONデータを送信するとサーバはこれをfile/txtファイルに記録し
|
とコンソールに表示する。
このクライアントはHttpClientオブジェクトを生成し、要求をするのにpost()メッソッドを使っている。要求をするために2つのFutureが関与している:
post()メソッドはサーバとのネットワーク接続を確立し最初のFutureで完了する、こちらのFutureは HttpClientRequestオブジェクトを返す。
クライアントは要求オブジェクトを組み立ててそれを閉じる。close()メソッドはその要求をサーバに送信し、2番目のFutureを返す。このFutureは HttpClientResponseオブジェクトで完了する。
|
basic_writer_client.dart
/*1*/ post()メソッドはhost、port、及び要求しているリソースへのパスを必要とする。HttpClientクラスはpost()に加えて、postUrl()、get()、及びopen()等の要求をするメソッドを有している。
/*2*/ HttpClientRequestのオブジェクトは要求ヘッダたちを指定するHttpHeadersオブジェクトを有している。 contentTypeのようなヘッダはそのヘッダに固有なプロパティたちを有している。他のヘッダたちに対しては、HttpHeadersのなかに置くためのset()メソッドを使用する。
/*3*/ このクライアントはrequestオブジェクトにデータを書くのにwrite()を使っている。JSONというエンコーディングは ContentTypeヘッダのなかで指定したタイプに合ったものである。
/*4*/ close()メソッドはこの要求をサーバに送信し、それが完了したら HttpClientResponseオブジェクトを返す
/*5*/ サーバからのUTF-8応答はデコードされる。このデータを通常のDartの文字列の型に変換するには dart:convertライブラリのなかで定義されているtransformerを使用する。
GET要求と同じく、RESTではPOST要求に対するガイドラインが存在する。
POST要求は:
リソースを生成する(この場合はファイル)
ファイルとディレクトリのパス名と同じような構造を持つURIを使う;例えばこのURIはクエリ文字列を持っていない
データはJSONまたはXMLとして転送する
長さに制限はない。
このサンプルでのクライアントはREST対応のPOST要求をしている。
HttpRequestオブジェクトはバイトのリスト(Stream<List<int>>)である。クライアントからのデータを取得するには HttpRequestオブジェクト上でのデータをリスンする。クライアントからののデータが大量のデータでできているときは、そのデータは複数のチャンク(かたまり)たちで到着しうる。これらのチャンクの文字列の値を連結するにはStreamのjoin()メソッドが使える。
|
basic_writer_server.dartファイルはましくこのパタンに従ったサーバを構成している。
|
/*1*/ この要求はHttpHeadersオブジェクトを持っている。クライアントが contentTypeヘッダにJSON (application/json)をセットしたことを思いだして頂きたい。このサーバは JSONエンコードでない要求は排除している。
/*2*/ POST要求は送信できるデータ量に制限がなく、複数のチャンクとして送信してくるかもしれない。更に、JSONはUTF-8であり、UTF-8文字は複数のバイトとしてエンコードされ得る。join()メソッドがこれらのチャンクを連結してくれる。
/*3*/ クライアントからのデータはJSONフォーマットである。サーバは dart:convertライブラリにある JSON codecを使ってデコードする。
/*4*/ 要求からのURLは localhost:4049/file.txtである。 req.uri.pathSegments.lastというコードはこの URI: file.txtからファイル名を抽出する。
CORSは(Cross-Origin Resource Sharing)の略語である。
セキュリティに問題があるので、ブラウザは要求に対しては組み込みアプリケーション(スクリプト)を作ったと同じサイトにあるものに限定している。逆に言えば要求をするコードは要求されるリソースと同じオリジン(ドメイン名、ポート番号、およびアプリケーション層プロトコル)からサービスされるものに限定される。つまり要求を行うコードを提供したと同じオリジン以外に属するリソースにアクセスできない。そうしておかないと、悪意のあるスクリプトがユーザの情報を別のサーバに送信させること(クロス・サイト・スクリプティング)を防げない。上記の例では、file.txtファイルはそれを使うアプリケーションと一緒に存在しなければならない。しかしCORSヘッダまたはJSONPを使えば、この制約を回避できる得る。
CORSでは、クロス・サイト・アクセスを行うクライアント(ブラウザ)側とクロス・サイト・アクセスされるサーバー側のふるまいが規定されている。クロス・サイト・アクセスされるサーバー側ではアクセスを制御するルールを設定し、ブラウザとサーバー側でHTTPヘッダを使ってアクセス制御に関する情報をやりとりしながらサイトをまたいだアクセスをおこなう。
サーバー側で設定するアクセスを制御するルール:
クロスドメインアクセスを許可するWebページのオリジン・サーバーのドメイン
使用を許可するHTTPメソッド
使用を許可するHTTPヘッダ
サーバ側(例えばnote_server.dart)では、この3つのルールを実装するとともに、これらをHTTP応答ヘッダに付加してブラウザのクライアントに返している:
|
CRRSヘッダ対応のブラウザは通常次のどれかを実行する:
とにかく直接クロス・サイトのサーバのリソースにアクセスするHTTP要求を送信する
あらかじめそのサイトがクロス・サイトのアクセスが可能かどうかを知るためにHTTP要求を送信する
ここではサービスへの登録が不要な天気予報のウェブ・サービスを使った簡単なアプリケーションのクライアントとサーバのコード例を示す。
天気予報ウェブ・サービスはグローバルにはOpenWeatherMapが良く使われている。このサービスのAPIは非常に豊富である。都市名、緯度経度、ZIPコードなどで指定した場所の現在の気象データ、5日間または16日間の予報、歴史データ、気象地図たちが得られる。このサイトはCORS対応しているのでブラウザだけでデータを取得できる。JavaScriptによるサンプル・コードは豊富に存在するので、これをDartに変換すれば良く、読者の練習にもなろう。
しかしここでは日本のLivedoorのお天気Webサービス(Livedoor Weather Web Service / LWWS)を使ってみる。LWWSは現在全国142カ所の今日、明日、および明後日の天気予報、予想気温、および都道府県の天気概況情報を提供している。
JavaScriptやDartなどのスクリプトを使ってこのウェブ・サービスをブラウザから直接アクセスするとCORSに対応していない為、エラーが発生し、ブラウザのJavascriptコンソールには次のように表示される:
|
従ってここではサーバが仲介役となってLivedoorのお天気Webサービスにアクセスする。この方式はサーバ側からウェブ・サービスにアクセスしており、これはサーバ・サイドのマッシュアップ・アプリケーションのプログラム開発のベースにもなる。
このアプリケーションはgithubに'weather_forecast_server'として公開してあるので、読者はダウンロードして実際に試してみると理解がはやくなる。
IDE上でserver\pubspec.yamlを開き、その状態でGet dependenciesを実行する
client\pubspec.yamlを開き、Get dependencies及び及びBuildコマンドを実行する
buildを実行した後は下図のような構成となっている:
|
server\bin\server.dartを実行させるとサーバが立ち上がる
次にChrome、Dartium、Firefox、Safari及びIE-11などのブラウザからから"http://127.0.0.1:8080/weather" でこのサーバをアクセスすると、初期画面(即ち現時点で得られる東京の予報)が表示される。「都市を選択」の個所の選択メニューから知りたい都市を選択すれば、その都市の予報が表示される。
下図はこのアプリケーションのクライアントの初期画面を示す:
|
ここではサーバは基本的に中継役(プロキシ)になり、JSONファイルの中身は関与させないサービスを考えることとする。こうしたのは、ブラウザ側でどのようにJSONファイルを処理するかのサンプルとする為である。
下図はサーバ側の構成とデータの流れである:
|
サーバ側では以下のファイルたちが置かれている:
/client/webに置かれたファイルたちはクライアント側の為のファイルである。
/client/buildに置かれたファイルたちは以下の3つのファイルが含まれている。これらのファイルはクライアントのブラウザからの要求に基づきサーバがクライアントに渡す:
client.dart.jsはclient.datをJavaScriptコードにコンパイルしたファイルである
client.htmlは/client/webに置かれた同じ名前のファイルのコピー
client.cssは/client/webに置かれた同じ名前のファイルのコピー
/server/binに置かれたファイルがサーバの実行ファイルたちである。server.dartがこのサービスの為のコードであり、残りはサーバ開発のテストに使われる。
/server/resoucesはクライアントに渡すリソースを収用する。初期はfaviconイメージのみであるが、クライアントと交信する中で天気のアイコンが蓄積されてゆく。
ブラウザ側での動作は次のようになる:
ブラウザはhttp://localhost:8080/weatherでこのサーバを呼び出す
サーバは/client/build/client.htmlを返す
ブラウザはこのHTMLで指定されている/weather/client.dart.jsを要求し、サーバは/client/build/client.dart.jsを返す
ブラウザは画面作成に必要な/weather/client.cssを要求し、サーバは/client/build/client.cssを返す
必要に応じブラウザはf/weather/avicon.icoを要求し、サーバは/resources/favicon.icoを返す
次にブラウザはデフォルト東京の天気情報を/weather/lwws?city=10040などを要求し、サーバはこれをLWWSのサイトからその都市に対するJSONデータを取得しこれをブラウザに返す
ブラウザは画面表示に必要な天気のアイコン(例えば曇り、晴れ、晴れのち雨など)を/weather/lwws?image=1.gifなどと要求し、サーバは/resources/の中にキャッシュされている.gifファイルを返す
もしそのファイルがキャッシュされていないときは、サーバはそれをLWWSから取り寄せ蓄積するとともに、ブラウザに返す
ブラウザがある都市の天気予報を表示するのに必要なデータ次のようである:
/weather/lwws?city=10040など |
LWWSからのその都市のJSONデータ |
/weather/lwws?image=1.gifなど |
LWWSからのその日の天気(雨のち晴れなど)のアイコンで、このサーバが保管していない場合はLWWSから取り寄せる保管する |
このことから、サーバはファイル・サーバの機能と、LWWSのプロキシの機能で構成されている。
このような一連の流れは、サーバのログを見れば良くわかる。クライアントが要求したURIは赤で、それに対する応答は緑で、サーバがプロキシとしてLWWSに要求したものは橙で示してある。
|
クライアントが要求したデータの順序とサーバが返したデータの順序は必ずしも一致していないことに注意する。例えばクライアントがhtml、client.dart.js、client.css、LWWSのJSONデータ、favicon.icoの順番に要求しているのに、サーバはhtml、client.css、client.dart.js、favicon.ico、LWWSのJSONデータの順に返している。これはクライアントからの各要求に対する処理が総て非同期で行われている為である。
クライアントは送られてきたJSONデータをもとに本日、翌日、及び明後日の天気情報を表示するのに必要なイメージの5.gif、13.gif、及び10.gifを要求しているが、そのうち最初の2つはサーバがファイルとして持っているので、直ちに返される。しかし残った10.gifはプロキシ経由でLWWSサーバから取り寄せ、それをサーバがファイルとして保存してからクライアントに返しているので少し時間がかかっている。しかしながらいずれは総てのイメージがサーバに蓄積されるので、それは余り気にする必要はなかろう。
クライアント即ちブラウザのコードはサーバと交信する必要がある。ブラウザで使われるAPIのライブラリであるdart:htmlはJavaScriptに対応する巨大なものであるが、その中にはHTTPでサーバにアクセスする為のHttpRequestというクラスがある。dart:ioのHttpRequestと間違えないよう注意が必要である。このクラスはこの章の終わりに翻訳してある。
ウェブ・サービスにアクセスする場合は次のようにHttpRequest.getStringというstaticメソッドが良く使われる:
var path = 'myData.json'; HttpRequest.getString(path) .then((String fileContents) { print(fileContents.length); }) .catchError((Error error) { print(error.toString()); |
これはURLを指定して文字列の応答を非同期で取得するものである。受信が終了したらthenで受ける。catchError はErrorの型が帰ると記載されているが、現在はそうなっていないのでバグ報告をあげてある。
具体的にはclient.dartでは次のような記述となっている:
|
ここで注意しなければならないのは、String型のオブジェクトを非同期で返す関数とするためにCompleterを用意し、最初にcompleter.futureを返し、String型のオブジェクトをcompleter.complete(responseText)でそのfutureを完了させるスタイルをとっていることである。一般に.thenで囲っていく(又はawaitで繋いでいく)ことでその最後の.then(又はawait)のなかでその処理を完了させるが、このように結果をFutureとして返す関数とするにはそのようなスタイルは使えない。
loadDataというFuture<String>を返す関数として記述されている。
HttpRequest.getStringはFuture<String>を返すので、これをthenで受け、得られたテキスト返しfutureを完了させる。
catchErrorは200 OK応答以外の応答が帰ってきたときなどで発生するイベントを受ける。
更にクライアント側では受理したJsonオブジェクトをもとにリッチな画面を作るので、DOMが中心になるので、これのポイントを以下に解説する。
更にクライアント側では受理したJsonオブジェクトをもとにリッチな画面を作るので、DOMが中心になるので、これのポイントを以下に解説する。
client.dartに比べclient.htmlとclient.cssはシンプルなものとなっている。その分DOMベースのdartコードがダイナミックな画面を生成する。JavaScriptのDOMを理解している読者であれば、client.dartは比較的容易に追うことが可能であろう。このコードはクライアントのコードを書く場合のテンプレートとしても使えよう。但しモバイル向けのアプリではFlutterが使われ、その場合は別の記述でよりリッチな画面が書ける。
LWWS(Livedoor Weather Web Service)から(及びプロキシであるserver.dartから)返されてくるJSONテキストは、適当なJSONエディタでその構成を調べると次のようになっている:
object {8} pinpointLocations [53] link : http://weather.livedoor.com/area/forecast/130010 forecasts [2] location {3} publicTime : 2014-10-07T05:00:00+0900 copyright {4} title : 東京都 東京 の天気
|
このjsonObjectオブジェクトは8個の要素からなるMapである
pinpointLocationsはピンポイント情報を得るためのリンク先である(今回は使用しない)
linkは東京地方の詳細天気予報を知るためのURLである(今回は使用しない)
forecastsは今日、明日、明後日(もしあれば)の予報のListである
locationは都市、地域、都道府県の名前のMapである
publicTimeはこの予報の発表時刻のMapである
copyrightは著作権情報のMapである
titleは概況のタイトルのMapである
descriptionは概況と発表時刻のMapである
これをもとにJSON.decodeで得られるJsonObjectから必要なデータを取り出せばよい。
サーバはサービスの構成の項で示したように、大きくはLWWSのプロキシの機能とファイル・サーバの機能が中心となっている。とりわけプロキシの機能(ここではLWWSインターフェイスとも記してある)は今回初めて登場するので、これを中心に説明する。
LWWSへのサーバからのアクセス
Dart:ioにはHTTPでサーバと交信する用途の為にHttpClientという抽象クラス(日本語に訳したものはこの章の終わりにある)が用意されている。つまりサーバが対象とするウェブ・サービスのクライアントとなる。サーバウェブ・サービスにたいしHttpClientRequestを送信し、その応答をHttpClientResponseとして受理する。
HttpClientは別のHTTPサーバに対しHttpClientRequestを送信し、戻ってくる HttpClientResponseを受信するための一連のメソッドたちを含む。例えば、GETおよびPOST要求に対しget, getUrl, post,およびpostUrlメソッドが使える。
一番シンプルなコードは次のような記述になる:
HttpClient client = new HttpClient(); client.getUrl(Uri.parse("http://www.example.com/")) .then((HttpClientRequest request) { // 必要ならヘッダたちをセット... // 必要なら要求オブジェクトに対しデータを書き込む... // その後HttpClientRequestのcloseを呼ぶ(Future<HttpClientResponse>が返される) ... return request.close(); }) .then((HttpClientResponse response) { // Process the response. ...
|
getUrlメソッドはサーバにGET要求をするためのオブジェクトを生成し、サーバとの接続を行う
これが完了したら、HTTP要求をそのサーバに送るためのHttpClienRequestのオブジェクトが渡されるので、そのオブジェクトに対しヘッダやボディ部をセットする
HttpClientRequestのcloseメソッドはHTTP要求をサーバに送信する(ヘッダ部は先に送信されている場合がある)。デフォルトではボディ部はGZIP圧縮して送信される
その後サーバからの応答を受信したらFuture<HttpClientResponse>が返されるので、thenで受けて受信した応答の処理を行う
サーバからLWWSのような認可や登録を必要としないウェブ・サービスをアクセスする一般的なコードは次のようになる。このコードはserver/bin/lwws_access_test.dartとしてこのアプリケーションのなかに同梱してあるので、これを直接自分のIDEから実行させて試して頂きたい。
|
幾つかのポイントを示すと:
サーバからの応答ヘッダにはcontent-type: application/json; charset=utf-8となっていて、UTF-8でエンコードされているので、transform(UTF8.decoder)でデコードしてとりだす。
またボディ部はtransfer-encoding: chunkedとヘッダに記されているように、チャンクで送信されてくるので、これを文字列として連結する必要がある。
最終的にデータはonDoneのコールバックで得られる。
JSON.decodeメソッドはJsonオブジェクトと呼ばれるMapで得られる。
server.dartの場合は_getBytesという関数で実現されている。
この関数もFutureを返し、非同期で進行する。この関数ではイメージ・ファイルも受け取るの為、バイト列としてそのままクライアントに転送しなければならないので、UTF-8変換することなくFuture<List<int>>の形で結果をわたす。また200(OK)以外の応答が帰ってきたときは、404(NOT FOUND)応答をクライアントに返している。
|
オリジナル・ドキュメント:https://api.dartlang.org/stable/2.2.0/dart-html/HttpRequest-class.html
HttpRequest class あるURLからデータを取得する為のクライアント側のXHR(XMLHttpRequest)要求で、以前はXMLHttpRequestとして知られていたもの。 HttpRequestはHTTPおよびFTPプロトコルでデータを取得するのに使え、AJAXスタイルのページの更新に有用である。 JSONフォーマットされたファイルのようなテキスト・ファイルの中身を取得する最もシンプルな方法は、getStringメソッドを使うことである。例えば、以下のコードはJSONファイルの中身を取得しその長さをプリントする:
他のサーバからのデータの取得について。
セキュリティの理由から、ブラウザは組み込みアプリケーションからの要求には制限を課している。このクラスのデフォルトの振る舞いでは、この要求をするコードは要求されているリソースと同じオリジン(ドメイン名、ポート番号、およびアプリケーション層プロトコル)から渡されねばならない。上記の例では、myData.jsonファイルはこれを使うアプリケーションと共に置かれて居なければならない。この制約はCORSヘッダまたはJSONPを使って回避し得る。 |
|||||||||||||||||||
実装 |
|||||||||||||||||||
Object→EventTarget→HttpRequestEventTarget→HttpRequest |
|||||||||||||||||||
コンストラクタ |
|||||||||||||||||||
HttpRequest() |
どのタイプの要求(GET、POST等)にも使える汎用コンストラクタ。 この呼び出しはopenと一緒にして使われる:
これは以下と等価であるが、ややくどい記述である:
|
||||||||||||||||||
メソッド |
|||||||||||||||||||
void abort() |
現行の要求を止める。 readyState がHEADERS_RECIEVED または LOADINGの状態である時のみこの要求は停止できる。このメソッドが送信中でないときは、このメソッドは効果を持たない。 |
||||||||||||||||||
String getAllResponseHeaders() |
(安定していない) ある要求からの総ての応答ヘッダをとりだす。 ヘッダを受信していないときはnullを返す。マルチパート要求の場合は、 getAllResponseHeadersはその要求の現行のパートに対する応答ヘッダを返す。 |
||||||||||||||||||
String getResponseHeader(String header) |
(安定していない) 指定したヘッダをもった応答を返し、見つからないときはnullを返す。 |
||||||||||||||||||
void open(String method, String url, {bool async, String user, String password}) |
この要求をするときはurlとmethodを指定する。 デフォルトでは、この要求は非同期で行われ、パスワード認証情報を持たない。もしasyncがfalseなら、この要求は同期的に送信される。 現行のアクティブな状態の要求にopenを再度呼ぶことはabortを呼ぶことと等価である。 注意:ほとんどのシンプルなHTTP要求はgetString、request、 requestCrossOrigin、またはpostFormData のメソッドを使って達成される。このopenメソッドを使うのは、細かなコントロールが必要なより複雑なHTTP要求にの場合のみに意図されている。 |
||||||||||||||||||
void overrideMimeType(String override) |
その要求にとって必要な特定のMIMEタイプ(例えばtext/xml のような)を指定する。 この値はその要求が送信される前にセットされねばならない。一般的な応答ヘッダのリストに対すしてはHTTP応答ヘッダを見ること。 @SupportedBrowser(SupportedBrowser.CHROME), @SupportedBrowser(SupportedBrowser.FIREFOX), @SupportedBrowser(SupportedBrowser.SAFARI) |
||||||||||||||||||
void send([data]) |
与えられたdataでその要求を送信する。 注意:殆どのシンプルなHTTP要求はgetString、request、 requestCrossOrigin、またはpostFormData のメソッドを使って達成される。このopenメソッドを使うのは、細かなコントロールが必要なより複雑なHTTP要求にの場合のみに意図されている。 他のリソース: XMLHttpRequest.send from MDN. |
||||||||||||||||||
void setRequestHeader(String header, String value) |
あるHTTP要求ヘッダの値をセットする。 このメソッドはこの要求がopenした後で且つ該要求が送信される前に呼ばれねばならない。 同じヘッダに対し複数回呼ぶとそれらは単一ヘッダとしてまとめられる。 他のリソース: XMLHttpRequest.setRequestHeader method from MDN. setRequestHeader() method-from W3C. |
||||||||||||||||||
void addEventListener(String type, EventListener listener, [bool useCapture]) |
EventTargetから継承 |
||||||||||||||||||
bool dispatchEvent(Event event) |
EventTargetから継承 |
||||||||||||||||||
noSuchMethod(Invocation invocation) |
Interceptorから継承
ユーザがあるオブジェクトに対し存在しないメソッドを呼んだときにnoSuchMethodが呼び出される。この呼び出しの名前と引数たちは Invocationのなかの noSuchMethodに渡される。noSuchMethod が値を返す時は、その値がオリジナルの呼び出しの結果となる。
noSuchMethodのデフォルトの振る舞いは NoSuchMethodErrorをスローすることである。 |
||||||||||||||||||
void removeEventListener(String type, EventListener listener, [bool useCapture]) |
EventTargetから継承 |
||||||||||||||||||
String toString() |
EventTargetから継承
このオブジェクトの文字列表現を返す。 |
||||||||||||||||||
演算子 |
|||||||||||||||||||
bool ==(other) |
EventTargetから継承 対等性演算子。 総てのObjectにたいする振る舞いはthisとotherが同じオブジェクトであるときに限りtrueを返す。 あるクラス上で別の対等性の関係を規定するときはこのメソッドをオーバライドする。オーバライドするほうのメソッドはそれでも対等性の関係を保持しなければならない。即ち、以下のごとくあらねばならない: Total: それは総ての引数に対しブール値を返さねばならない。決してスローしたりnullを返してはいけない。
このメソッドは近いとともに一貫していなければならないので、2つのオブジェクトは時間とともに変化してはいけないか、あるいは少なくとも一つのオブジェクトに変化を与えられたときにのみ変化する。 あるサブクラスがこの演算子をオーバライドするときは、一貫性を保つためにはhashCodeもオーバライドしなければならない。 |
||||||||||||||||||
Staticメソッド |
|||||||||||||||||||
static Future<String> getString(String url, {bool withCredentials, Function void onProgress(ProgressEvent e)}) |
指定したurlむけのGET要求を生成する。 この要求が成功するにはサーバの応答は text/ mimeタイプでなければならない。 これはrequestと似ているがテキストの中身を返すHTTP GET 要求に特化している。 クエリ・パラメタを付加するには、urlのあとに?を付けそのあとにそれらを追加する。その際各キーと値は=で結び付け、各キーと値のペアを&で分離する。
See also: request |
||||||||||||||||||
static Future<HttpRequest> postFormData(String url, Map<String, String> data, {bool withCredentials, String responseType, Map<String, String> requestHeaders, Function void onProgress(ProgressEvent e)}) |
指定したdataをformデータとして付したPOST要求をサーバにする。 これはほぼgetStringのPOST等価なメソッドである。このメソッドはより広いブラウザ・サポートを持つ FormDataオブジェクトを送信することと等価であるがStringの値に限定されている。 もしdataが与えられたら、そのキー/値のペアは encodeQueryComponentでURIエンコードされ、HTTPクエリ文字列に変換される。 指定されていない限り、このメソッドは以下のヘッダを追加する:
以下はこのメソッドの使用例である:
See also: request |
||||||||||||||||||
static Future<HttpRequest> request(String url, {String method, bool withCredentials, String responseType, String mimeType, Map<String, String> requestHeaders, sendData, Function void onProgress(ProgressEvent e)}) |
指定したurlに対する要求を生成し送信する。 デフォルトではrequestはHTTP GET要求を行うが、methodパラメタを指定することで他のメソッド(POST, PUT, DELETE, etc)も使用可能である。(POST要求だけの場合のpostFormDataも参照のこと)返されるFutureはその応答が得られたら完了する。 指定されていれば、sendData は ByteBuffer, Blob, Document, String,またはFormDataの形式のデータをHttpRequestに載せて送信する。 指定されていれば、responseType はその要求に対する欲しい応答書式をセットする。デフォルトではそれはStringであるが、'arraybuffer', 'blob', 'document', 'json', または'text'が指定できる。更なる情報は responseType を参照のこと。 withCredentialsパラメタは(既に)クッキーのようなクレデンシャルがヘッダにセットされているか、その要求にauthorization ヘッダを指定するかしなければならない。クレデンシャルを使うときの注意点:
これは上記の getStringサンプルと等価なものである:
以下はFormDataでフォーム全部をサブミットする例である:
file:// URIsへの要求はそのマニフェストでしかるべき許可をしたChrome拡張でのみサポートされる。file:// URIsへの要求はまた決して失敗は起きず、例えそのファイルが見つからないときでも常に成功で完了する。 See also: authorization headers. |
||||||||||||||||||
static Future<String> requestCrossOrigin(String url, {String method, String sendData}) |
指定したURLにクロス・オリジン要求を行う。 このAPIは IE9で動作するサブセットである。もしIE)のクロス・オリジン・サポートが必要ないときは、その代りrequestを使うべきである。 |
||||||||||||||||||
属性 |
|||||||||||||||||||
Stream<ProgressEvent> get onReadyStateChange |
このHttpRequestEventTargetで扱われたreadystatechange イベントのストリーム。 イベント・リスナたちはこのHttpRequestオブジェクトの readyStateの値が変わった時毎に通知される。 |
||||||||||||||||||
final int readyState |
この要求の現在の状態を示すインディケータ:
|
||||||||||||||||||
get response |
その要求からの応答として受信したデータ。 データはString, ByteBuffer, Document, Blob, or json (also a String)であり得る。nullは要求が失敗したことを示す。 @SupportedBrowser(SupportedBrowser.CHROME), @SupportedBrowser(SupportedBrowser.FIREFOX), @SupportedBrowser(SupportedBrowser.IE, '10'), @SupportedBrowser(SupportedBrowser.SAFARI), read-only |
||||||||||||||||||
Map<String, String> get responseHeaders |
総ての応答ヘッダたちをキー/値のMapとして返す。 同じヘッダ・キーに複数の値があるときは一つにまとめられ、カンマとスペースで区切られる。 See: http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader()-method |
||||||||||||||||||
final String responseText |
Stringの形式の応答で、空のStringの時は失敗したことを示す。 |
||||||||||||||||||
String responseType |
サーバに対し欲しい応答のフォーマットを告げるためのString。 デフォルトはStringだが、それ以外に'arraybuffer', 'blob', 'document', 'json', 'text'のどれかを選択できる。 同期要求を行っている際に responseTypeがセットされているときに一部のブラウザでは NSERRORDOMINVALIDACCESS_ERRをスローする。 See also: MDN responseType |
||||||||||||||||||
final String responseUrl |
@JSName('responseURL'), final |
||||||||||||||||||
final Document responseXml |
要求に対する応答で、失敗したときはnullである。 その応答はresponseType = 'document'でその要求が同期でないときに text/xmlのストリームとして処理される。 @JSName('responseXML'), final |
||||||||||||||||||
final int status |
その要求に対する応答の結果コード(200、404等) See also: Http Status Codes |
||||||||||||||||||
final String statusText |
応答ステータスの文字列(such as \"200 OK\")。 See also: Http Status Codes |
||||||||||||||||||
int timeout |
ある要求が自動的に終了するまでの時間長。timeを超過したらTimeoutEvent で通知される。timeを0にセットすると、この要求はタイムアウトを起こさない。 Other resources XMLHttpRequest.timeout from MDN. The timeout attribute from W3C |
||||||||||||||||||
bool withCredentials |
クロス・サイトの要求がクッキーのようなクレデンシャルを使うかまたは authorizationヘッダを使うかするときはtrueとする。それ以外はfalseである。 同じサイトへの要求ではこの値は無視される。 |
||||||||||||||||||
int get hashCode |
EventTargetから継承 このオブジェクトのハッシュ・コードを取得する。 |
||||||||||||||||||
HttpResponseUpload upload |
その要求の進捗状況を追跡するためにリスナたちを保持できるEventTarget。ファイアされるイベントは HttpRequestUploadEventsのメンバとなる @Unstable(), final |
||||||||||||||||||
Events get on |
EventTargetから継承 これはイベント・ストリームに対する使いやすいアクセサであって、明示的なアクセサがないときにのみ使われるべきである。 |
||||||||||||||||||
Stream<ProgressEvent> get onAbort |
HttpRequestEventTargetから継承 このHttpRequestEventTargetで扱われたabortイベントのストリーム。 |
||||||||||||||||||
Stream<ProgressEvent> get onError |
HttpRequestEventTargetから継承 このHttpRequestEventTargetで扱われたerrorイベントのストリーム。 |
||||||||||||||||||
Stream<ProgressEvent> get onLoad |
HttpRequestEventTargetから継承 このHttpRequestEventTargetで扱われたloadイベントのストリーム。 |
||||||||||||||||||
Stream<ProgressEvent> get onLoadEnd |
HttpRequestEventTargetから継承 このHttpRequestEventTargetで扱われたloadendイベントのストリーム。 @SupportedBrowser(SupportedBrowser.CHROME), @SupportedBrowser(SupportedBrowser.FIREFOX), @SupportedBrowser(SupportedBrowser.IE, '10'), @SupportedBrowser(SupportedBrowser.SAFARI), |
||||||||||||||||||
Stream<ProgressEvent> get onLoadStart |
HttpRequestEventTargetから継承 このHttpRequestEventTargetで扱われたloadstartイベントのストリーム。 |
||||||||||||||||||
Stream<ProgressEvent> get onProgress |
HttpRequestEventTargetから継承 このHttpRequestEventTargetで扱われたprogressイベントのストリーム。 @SupportedBrowser(SupportedBrowser.CHROME), @SupportedBrowser(SupportedBrowser.FIREFOX), @SupportedBrowser(SupportedBrowser.IE, '10'), @SupportedBrowser(SupportedBrowser.SAFARI) |
||||||||||||||||||
final HttpRequestUpload upload |
該要求の進捗状況を追跡するためのリスナを保持できるEventTarget。ファイアするイベントは HttpRequestUploadEventsのメンバとなる。 @Unstable(), final |
||||||||||||||||||
Stream<ProgressEvent> get onTimeout |
HttpRequestEventTargetから継承
このHttpRequestEventTargetで扱われたtimeoutイベントのストリーム。 |
||||||||||||||||||
Type get runtimeType |
Interceptorから継承
このオブジェクトの実行時の型を表現する。 Comment inherited from Object |
||||||||||||||||||
Static属性 |
|||||||||||||||||||
static bool get supportsCrossOrigin |
現行のプラットホームがクロス・オリジン要求をするのに対応しているかどうかをチェックする。 注意:たとえクロス・オリジン要求に対応していても、相手のサーバがCORS要求に対応していなければその要求は失敗する。 |
||||||||||||||||||
static bool get supportsLoadEndEvent |
現行のプラットホームが LoadEndイベントに対応しているかどうかをチェックする。 |
||||||||||||||||||
static bool get supportsOverrideMimeType |
現行のプラットホームが overrideMimeType メソッドに対応しているかどうかをチェックする。 |
||||||||||||||||||
static bool get supportsProgressEvent |
現行のプラットホームがProgressイベントに対応しているかどうかをチェックする。 |
オリジナル・ドキュメント:https://api.dartlang.org/stable/2.2.0/dart-io/HttpClient-class.html
HttpClient class
HTTPプロトコルを使ってウェブ・ページのようなコンテントをあるサーバから受信するクライアント。 HttpClientはあるHTTPサーバに対し HttpClientRequestを送信し、戻ってくる HttpClientResponseを受信するための一連のメソッドたちを含む。例えば、GETおよびPOST要求に対しget, getUrl, post,およびpostUrlメソッドが使える。
シンプルなGET要求をする例: getUrl 要求は2つのFutureがトリガとなる2段階のプロセスである。HttpClientRequestで最初のFutureが完了したときは、その背後のネットワーク接続が確立したがデータはまだ送信されていない状態である。最初のFutureのコールバック関数の中で、HTTPヘッダたちとボディ部がその要求上でセットできる。その要求オブジェクトに最初に何かを書き込んだとき、あるいはcloseを呼んだときにその要求はサーバに送信される。 そのサーバからHTTP応答が受信したときは、closeで返された第2のFutureがHttpClientResponseオブジェクトで完了する。このオブジェクトが該応答のヘッダたちおよびボディへのアクセスを提供する。このボディは HttpClientResponseで実装されているストリームによって取得できる。もしボディ部が存在していれば、それは読み出しされねばならず、そうでないとリソースのリークをもたらす。ボディを使わないときはHttpClientResponse.drainを使うことを検討すること。
HttpClientRequestのFutureは getUrlおよびopenのようなメソッドによって生成される。
ヘッダ 総てのHttpClientの要求はデフォルトでは次のヘッダ行が付加される:
これにより該HTTPサーバはそのボディにたいし可能ならgzip圧縮をかける。こうしてほしくないときは、 Accept-Encodingヘッダをなにか別のものに変更する。応答のgzip圧縮をオフにするときは、このヘッダをオフにする:
HttpClientを閉じる HttpClientは出来うる限り複数の要求の為に再利用する為の永続した接続およびキャッシュ網接続に対応している。このことはある要求が完了した後もある時間ネットワーク接続を保持され得ることを意味する。強制的にこの接続をシャットダウンし、アイドル状態のネットワーク接続を閉じるには、HttpClient.close を使う。
プロキシのオンとオフ デフォルトではHttpClient はその環境で使えるプロキシ構成を使用する、findProxyFromEnvironmentメソッドを参照のこと。プロキシ使用をオフにするにはfindProxy属性をnullにする:
|
|||||
継承 |
|||||
Object |
|
||||
コンストラクタ |
|||||
HttpClient() |
|
||||
メソッド |
|||||
void addCredentials(Uri url, String realm, HttpClientCredentials credentials) |
HTTP要求を認可するために使われるクレデンシャル(証明書)たちを付加する。 |
||||
void addProxyCredentials(String host, int port, String realm, HttpClientCredentials credentials) |
HTTPプロキシたちを認可するために使われるクレデンシャルたちを付加する。 |
||||
void close({bool force: false}) |
該HTTPクライアントを閉じる。forceがfalseのとき(デフォルト)は総てのアクティブな接続が完了するまでこのHttpClientは活きつづける。forceがtrueのときは、アクティブな接続は閉じられ、総てのリソースが解放される。これらの閉じた接続たちは該接続がシャットダウンしたことを示す為のonErrorコールバックを受ける。双方の場合においてシャットダウンを呼んだ後で新規の接続を確立しようとすると例外が生起する。 |
||||
Future<HttpClientRequest> delete(String host, int port, String path) |
HTTPのDELETEメソッドを使ってHTTP接続を行う。 このサーバはhostとportを使って指定され、パス(フラグメントとクエリが付加されている場合もあり)はpathを使って指定される。 詳細はopenを参照のこと。 |
||||
Future<HttpClientRequest> deleteUrl(Uri url) |
HTTPのDELETEメソッドを使ってHTTP接続を行う。 使用するサーバのURLはurlで指定される。 詳細はopenUrl を参照のこと。 |
||||
Future<HttpClientRequest> get(String host, int port, String path) |
HTTPのGETメソッドを使ってHTTP接続を行う。 このサーバはhostとportを使って指定され、パス(フラグメントとクエリが付加されている場合もあり)はpathを使って指定される。 詳細はopenを参照のこと。 |
||||
Future<HttpClientRequest> getUrl(Uri url) |
HTTPのGETメソッドを使ってHTTP接続を行う。 使用するサーバのURLはurlで指定される。 詳細はopenUrl を参照のこと。 |
||||
Future<HttpClientRequest> head(String host, int port, String path) |
HTTPのHEADメソッドを使ってHTTP接続を行う。 このサーバはhostとportを使って指定され、パス(フラグメントとクエリが付加されている場合もあり)はpathを使って指定される。 詳細はopenを参照のこと。 |
||||
Future<HttpClientRequest> headUrl(Uri url) |
HTTPのHEADメソッドを使ってHTTP接続を行う。 使用するサーバのURLはurlで指定される。 詳細はopenUrl を参照のこと。 |
||||
Future<HttpClientRequest> open(String method, String host, int port, String path) |
HTTP接続を行う。 使用するHTTPメソッドはmethodで指定され、このサーバはhostとportを使って指定され、パス(フラグメントとクエリが付加されている場合もあり)はpathを使って指定される。 要求の為のHostヘッダは host:portという値でセットされる。これはこの要求が送信される前であればHttpClientRequestインターフェイスを介してオーバライドされ得る。 注意:もしhostがIPアドレスの時は、これはHostヘッダにセットされたままとなる。 HTTPトランザクション中のイベントたちのシーケンス、およびfutureたちによって返されるオブジェクトたちに関する更なる情報は、HttpClientクラスの全体ドキュメンテーションを参照のこと。 |
||||
Future<HttpClientRequest> openUrl(String method, Uri url) |
HTTP接続を行う。 HTTPメソッドはmethodで指定し、URLはurlで指定する。 要求の為のHostヘッダは host:portという値でセットされる。これはこの要求が送信される前であればHttpClientRequestインターフェイスを介してオーバライドされ得る。 注意:もしhostがIPアドレスの時は、これはHostヘッダにセットされたままとなる。 HTTPトランザクション中のイベントたちのシーケンス、およびfutureたちによって返されるオブジェクトたちに関する更なる情報は、HttpClientクラスの全体ドキュメンテーションを参照のこと。 |
||||
Future<HttpClientRequest> patch(String host, int port, String path) |
HTTPのPATCHメソッドを使ってHTTP接続を行う。 このサーバはhostとportを使って指定され、パス(フラグメントとクエリが付加されている場合もあり)はpathを使って指定される。 詳細はopenを参照のこと。 |
||||
Future<HttpClientRequest> patchUrl(Uri url) |
HTTPのPATCHメソッドを使ってHTTP接続を行う。 使用するサーバのURLはurlで指定される。 詳細はopenUrl を参照のこと。 |
||||
Future<HttpClientRequest> post(String host, int port, String path) |
HTTPのPOSTメソッドを使ってHTTP接続を行う。 このサーバはhostとportを使って指定され、パス(フラグメントとクエリが付加されている場合もあり)はpathを使って指定される。 詳細はopenを参照のこと。 |
||||
Future<HttpClientRequest> postUrl(Uri url) |
HTTPのPOSTメソッドを使ってHTTP接続を行う。 使用するサーバのURLはurlで指定される。 詳細はopenUrl を参照のこと。 |
||||
Future<HttpClientRequest> put(String host, int port, String path) |
HTTPのPUTメソッドを使ってHTTP接続を行う。 このサーバはhostとportを使って指定され、パス(フラグメントとクエリが付加されている場合もあり)はpathを使って指定される。 詳細はopenを参照のこと。 |
||||
Future<HttpClientRequest> putUrl(Uri url) |
HTTPのPUTメソッドを使ってHTTP接続を行う。 使用するサーバのURLはurlで指定される。 詳細はopenUrl を参照のこと。 |
||||
staticメソッド |
|||||
static String findProxyFromEnvironment(Uri url, {Map<String, String> environment}) |
environment変数で指定されたプロキシ構成から、HTTP接続に使われるプロキシ・サーバを得るための関数。 以下のenvironment変数たちが調べられる:
http_proxyとHTTP_PROXYは http:// urlsに使われるプロキシ・サーバを指定する。hostname:portを使う。portがないときはデフォルトとして1080が使われる。双方ともにセットされていると小文字のほうが優先する。 https_proxyとHTTPS_PROXYは http:// urlsに使われるプロキシ・サーバを指定する。hostname:portを使う。portがないときはデフォルトとして1080が使われる。双方ともにセットされていると小文字のほうが優先する。 no_proxyとNO_PROXYはプロキシ・サーバに使わないhostnameたちをカンマで区切ったリストを指定する。例えば"localhost,127.0.0.1"という値は"localhost"と"127.0.0.1"の双方への要求にはプロキシを使わなくする。双方ともにセットされていると小文字のほうが優先する。 このプロキシ解決手段を活かすには、この関数を HttpClient上でfindProxyに代入する。
システム環境を使いたくないときは、この関数をラップして異なったものを使うことができる:
もしプロキシが認可を必要とする場合は、 usernameと passwordも設定することができる。usernameと passwordも含めるときは username:password@hostname:portという書式を使う。別のやりかたとして、 addProxyCredentialsというAPIが認可を要求とするプロキシにクレデンシャルをセットするのに使える。 |
||||
属性 |
|||||
set authenticate(Function Future<bool> f(Uri url, String scheme, String realm)) |
そのサイトが認可を要求している場合に呼ばれる関数をセットする。要求されるURLとサーバからのセキュリティ・レルムはurlとrealm変数で渡される。 この関数はFutureを返し、認証が終わった時に完了する。クレデンシャルたちが与えられない場合はFutureがfalseで終了する。クレデンシャルたちが指定されたときは、この関数はtrueのvalueでFutureが完了される前に addCredentialsを使ってこれらを付加しなければならない。 Futureがtrueで完了したときは、この要求は更新されたクレデンシャルたちを使って再度試行される。そうでないときは、応答処理は通常通り継続する。 |
||||
set authenticateProxy(Function Future<bool> f(String host, int port, String scheme, String realm)) |
プロキシが認可を要求している場合に呼ばれる関数をセットする。使用しているプロキシに関する情報とその認証の為のセキュリティ・レルムはhost、portとrealm変数で渡される。 この関数はFutureを返し、認証が終わった時に完了する。クレデンシャルたちが与えられない場合はFutureがfalseで終了する。クレデンシャルたちが得られるときは、この関数はtrueのvalueでFutureが完了される前に addProxyCredentialsを使ってこれらを付加しなければならない。 Futureがtrueで完了したときは、この要求は更新されたクレデンシャルたちを使って再度試行される。そうでないときは、応答処理は通常通り継続する。 |
||||
bool autoUncompress |
この応答のボディ部が自動的に圧縮されるかどうかを取得および設定する。 HTTP応答のボディ部は圧縮し得る。殆どの場合解凍したボディを渡すことが最も都合がよい。従ってデフォルトの振る舞いはボディ部は解凍されることになっている。しかしながらある状況(例えば透過プロキシの実装)では解凍しないストリームを維持することが求められる。 注意:応答からのヘッダは加工されることはない。このことは、自動回答がオンになっていても Content-Lengthヘッダはオリジナルの圧縮されたボディの長さを反映していることを意味する。同様に、 Content-Encodingヘッダは圧縮を意味するオリジナルの値を保持している。 注意:自動解凍はContent-Encodingヘッダの値がgzipであるときのみ行われる。 この値が変更された後は、このクライアントが生成する総ての応答にその値が適用される。 自動解凍をオフにするときは、falseをセットする。 デフォルトはtrueである。 |
||||
set badCertificateCallback(Function bool callback(X509Certificate cert, String host, int port)) |
我々の信頼されるルート認証たちのどれを使っても認可が得られないサーバ認証でセキュアな接続を受け付けるかどうかを判断するコールバック関数をセットする。 セキュアなHTTP要求がこのHttpClientを使ってでき、そのサーバが認可できなかったs-場認証を返したときは X509認証オブジェクトおよびそのサーバのhostnameとportでこのコールバックが非同期で呼ばれる。badCertificateCallback= の値がfalseのときは、その合わない認証は、あたかもそのコールバックがfalseを返したごとく排除される。 もしこのコールバックがtrueを返す時は、そのセキュアな接続は受け付けられ、その要求をしている呼び出しから返されたFuture<HttpClientRequest> は有効なHttpRequestオブジェクトで完了する。このコールバックがfalseを返す時は、Future<HttpClientRequest>は例外で完了する。 試みている接続上で認証できず(bad certificate)が受信されているとき、このライブラリはたとえそれ以来 badCertificateCallbackの値が変わっていたとしても、その要求がなされた時点での badCertificateCallbackの値である関数を呼び出す。 |
||||
set findProxy(Function String f(Uri url)) |
指定したurlに対しHTTP接続を開くのに使われるプロキシ・サーバを解決するのに使われる関数をセットする。この関数がセットされていないときは、常に直接接続が使われる。 fが返すStringはブラウザPAC (proxy auto-config)スクリプトで使われる書式でなければならない。即ち以下のどれかでなければならない: 直接接続使用の為の
またはポートport上のプロキシ・サーバhostを使うための
設定はセミコロンで区切られた幾つかの要素を含むことがあり得る。例えば:
このクラスのstatic関数のfindProxyFromEnvironmentが環境変数に基づくプロキシ・サーバ解決を実装するのに使える。 |
||||
Duration idleTimeout |
非アクティブな永続した(keep-alive) 接続のアイドルのタイムアウトの取得および設定。デフォルト値は15秒である。 |
||||
int maxConnectionsPerHost |
単一のホストに対するライブな接続の最大数の取得と設定。 この数を増やすと性能を下げ不必要なシステム・リソースの消費をもたらし得る。 これを殺す時はnullをセットする。 デフォルトはnullである。 |
||||
String userAgent |
この HttpClientが生成した総ての要求におけるデフォルトのUser-Agentヘッダの値の取得と設定。デフォルトの値はDart/<version> (dart:io)である。 userAgent がnullに設定されているときは、各要求にはデフォルトの User-Agentヘッダは付加されない。 |
||||
static属性 |
|||||
static const int DEFAULT_HTTPS_PORT |
|
||||
static const int DEFAULT_HTTP_PORT |
|
オリジナル・ドキュメント:https://api.dartlang.org/stable/2.2.0/dart-io/HttpClientRequest-class.html
HttpClientRequest class
あるクライアント接続の為のHTTP要求。 要求を準備するには、このクラスで用意されている headersプロパティを使ってヘッダたちをセットし、この要求のボディにデータを書き込む。 HttpClientRequestはIOSinkである。このHTTP要求のボディに書き込むにはwriteCharCode()のようなIOSinkのメソッドたちのひとつを使用する。IOSinkのメソッドたちのどれかが初めて使われると要求ヘッダが送信される。それが送信された後でヘッダを変えるようなメソッドが呼ばれると例外がスローされる。 このIOSinkを介して文字列データを書き込むときは使われるエンコーディングは"Content-Type"ヘッダの"charset"パラメタで決まる。
charsetが指定されていないときは、デフォルトのがISO-8859-1 (Latin 1)使われる。
対応していないエンコーディングを使い、stringパラメタを使うwrite()メソッドが使われていると例外がスローされる。 |
|||
継承 |
|||
コンストラクタ |
|||
HttpClient()HttpClientRequest() |
|||
メソッド |
|||
close() → Future<HttpClientResponse> |
入力の為のこの要求を閉じる。完了の値を返す。 override |
||
add(List<int> data) → void |
エンコーディングを無視してターゲットのコンシューマにバイト・データを加える。 inherited |
||
addError(Object error, [ StackTrace stackTrace ]) → void |
ターゲットのコンシューマにエラー・イベントとしてerrorを渡す。 inherited |
||
addStream(Stream<List<int>> stream) → Future |
指定したストリームの全要素をこれに加える。 inherited |
||
flush() → Future |
総てのバッファされていたデータがStreamConsumerで受け付けられたら完了するFutureを返す。 inherited |
||
noSuchMethod(Invocation invocation) → dynamic |
存在しないメソッドまたはプロパティがアクセスされたときに呼び出される。 inherited |
||
toString() → String |
このオブジェクトの文字列表現を返す。 inherited |
||
write(Object obj) → void |
Object.toStringを使ってobjをStringに変換し、その結果のエンコーディングしたものをターゲットのコンシューマに加える。 inherited |
||
writeAll(Iterable objects, [ String separator = "" ]) → void |
与えられたオブジェクト上で繰り返し、それらを順番に書き込む。 inherited |
||
writeCharCode(int charCode) → void |
charCodeの文字を書き込む。 inherited |
||
writeln([Object obj = "" ]) → void |
Object.toStringを使ってobjをStringに変換し、その結果に改行を加えてこれに書き込む。 inherited |
||
プロパティ |
|||
bufferOutput ↔ bool |
このHttpClientRequestがバッファ出力であるかどうかをセットまたは取得する。 read / write |
||
connectionInfo → HttpConnectionInfo |
当該クライアント接続に関する情報を取得する。 read-only |
||
contentLength ↔ int |
この要求のコンテント長を取得あるいはセットする。 read / write |
||
cookies → List<Cookie> |
このサーバに示すクッキーたち('cookie'ヘッダのなかの) read-only |
||
done → Future<HttpClientResponse> |
HttpClientResponseのFutureで、その応答が使えるようになったら完了する。 read-only, override |
||
followRedirects ↔ bool |
この要求は自動的にredirectに従うときはtrueをセットする。 デフォルトはtrueである。 read / write |
||
headers → HttpHeaders |
このクライアント要求のヘッダたちを返す。 read-only |
||
maxRedirects ↔ int |
followRedirectsがtrueの時に従うべきリダイレクトの最大回数をセットまたは取り出す。この回数を超過すると RedirectExceptionのエラー・イベントが付加される付加される。 read / write |
||
method → String |
該要求のPOSTとかGETなどのメソッド。 read-only |
||
persistentConnection ↔ bool |
要求されているパーシステント接続状態のセットまたは取得。 read / write |
||
uri → Uri |
該要求のURI。 read-only |
||
encoding ↔ Encoding |
文字列を書き込む際に使われる Encodingのセットまたは読み出し。下位層のコンシューマ次第でこのプロパティはリード・オンリーである。 read / write, inherited |
||
hashCode → int |
このオブジェクトのハッシュ・コード。 read-only, inherited |
||
runtimeType → Type |
このオブジェクトのランタイム時の型の表現。 read-only, inherited |