Upload
others
View
1
Download
0
Embed Size (px)
Citation preview
ORACLE.COM/JAVAMAGAZINE /////////////////// NOVEMBER/DECEMBER 2017
05
//java ee 8 /
長い間待ち望まれてきたJava EE 8には、JAX-RS 2.1、Bean Validation 2.0、JavaServer Faces(JSF)2.3、
Contexts and Dependency Injection(CDI)2.0、JSON with Padding(JSONP)1.1、Servlet 4.0といった、既
存APIのアップデートに加え、JSON-Binding(JSON-B)およびJava EE Securityという2つのまったく新しいAPIが含ま
れています。これらのAPIの中でも、Servlet 4.0は2009年にServlet APIが登場して以来の大幅なアップデートとなっ
ています。
このメジャー・リリース(ポイント・リリースではありません)を促す原動力となったのは、HTTP/2プロトコルが
世界中で展開されている点と、このプロトコルがもたらした多くの新機能です。このHTTPのアップデートは、約20年
間で初めてのことで、HTTP 1.xのさまざまな欠点に対処するものです。数々の新機能(リクエスト/レスポンスの多重
化、ヘッダー圧縮、ストリームの優先順位、サーバー・プッシュ)がありますが、Servlet APIユーザーにとってもっとも
注目の機能はサーバー・プッシュです。本記事では、このサーバー・プッシュについて取り上げます。
Servlet 4.0に追加された注目の新機能は、サーバー・プッシュだけではありません。今回のリリースには、
Servlet Mapping APIという形での機能改善も含まれています。このAPIでは、リファラーのパスを取得する方法を
改善することにより、マッピングの実行時検出を可能にしています。本記事では、これらの機能について解説すると
ともに、サーバー・プッシュがJavaServer Faces 2.3 APIとどのように統合されているかについて説明します。
サーバー・プッシュサーバー・プッシュ機能は、Webページのリソース要件に先回りして対応するために設計されたもので、リクエスト
の処理が完了する前に、イメージやCSSファイル、JavaScriptファイルなどのリソースをクライアントにプッシュしま
す。そのため、ブラウザがWebページのリクエストに対するレスポンスを受信するときには、必要なリソースがすで
に利用可能な状態でキャッシュに格納されているようになります。
これにより、ブラウザのTCP接続制限を回避するためにドメイン間でリソースをシャード(分割)する必要はな
Servlet 4.0:高速化HTTP/2プロトコルを含むServlet APIの新しいメジャー・リリースでは、リソース要件への先回り対応が実現
ALEX THEEDOM
ORACLE.COM/JAVAMAGAZINE /////////////////// NOVEMBER/DECEMBER 2017
06
//java ee 8 /
くなり、リソースを指定するだけで、サーバーが配信を処理してくれるようになります。これこそ、最初からあるべき
だった姿です。
ブラウザのキャッシュにリソースがあれば、ブラウザはページをはるかに高速にレンダリングできます。100を
超えるリソースが必要となる一般的なWebページでは、このことが、いかにパフォーマンスを向上させるかが容易
にわかります。
サーバー・プッシュの動作サーブレットは、Java EE Webアプリケーション層の背後にあるバックボーン・テクノロジーであり、多くのフレームワ
ークの基盤を形成しているサーバー機能を提供しています。JavaServer Faces(JSF)は、FacesServletを使ってWeb
アプリケーションのリクエスト処理のライフサイクル管理を行っています。また、JSPページは、クライアントからの
最初のリクエストでサーブレットに変換されます。そのため、サーブレットがHTTP/2サーバー・プッシュの抽象化の自
然な公開ポイントとなっていることには何の不思議もありません。
この抽象化を表しているのがPushBuilderオブジェクトです。このオブジェクトは、オーバーライドされているすべてのリクエスト処理メソッドに渡されるHttpServletRequestインスタンスのnewPushBuilder()メソッドを呼び出して作成します。
PushBuilderインスタンスを使うと、リクエストされたWebページで必要とされるリソースのプッシュを開始できます。リソースの設定は、PushBuilderインスタンスのpath()メソッドに場所を渡すことで行います。push()メソッドを呼び出すと、対象となったリソースがクライアントにプッシュされます。このインスタンスを再利用して、必要な
だけのリソースを送ることができます。
リスト1とリスト2は、サーバー・プッシュを使用するもっとも簡単な例を示したものです。リスト1に、URI /simplestexampleに対するGETリクエストに応答するHTTPサーブレットを示します。
リスト1:@WebServlet("/simplestexample")public class SimplestExample extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.newPushBuilder()
ORACLE.COM/JAVAMAGAZINE /////////////////// NOVEMBER/DECEMBER 2017
07
//java ee 8 /
.path("images/coffee-cup.jpg") .push(); getServletContext() .getRequestDispatcher("/coffee-cup.jsp") .forward(request, response); }}
リスト2に、JSP Webページを示します。
リスト2:<html><head> <title>Coffee-Cup</title></head><body> <img src='images/coffee-cup.jpg'></body></html>
リスト1では、SimplestExampleというサーブレットと、サーブレットをディスパッチするJSPを定義しています。おわかりのように、このJSPページで必要なリソースは、coffee-cup.jpgというイメージ1つだけです。doGet()リクエスト処理メソッドが呼び出されると、新しいPushBuilderインスタンスが作成されます。ここにイメージの場所を設定してpush()メソッドを呼び出すと、クライアントにリソースが送信されます。
イメージがクライアントに向かうのに合わせて、サーブレットは、coffee-cup.jpgリソースを必要とするJSPにリクエストを転送します。送信されるHTMLをブラウザが受け取るときには、すでにイメージがキャッシュに格納され
ているため、別のリクエストを行わずにページを表示できます。
GoogleのChromeブラウザが提供している開発者ツールを使って、サーバー・プッシュの動作を確認できます。
「その他のツール」→「デベロッパー ツール」を選択し、「Network」タブをクリックしてから、SimplestExampleサーブレットをリクエストすると、図1に示すものと同じような結果が表示されるはずです。リクエストで使われているプロトコルがh2(「HTTP/2」の省略形)であり、イメージのイニシエータがプッシュであったことがはっきりとわかり
ます。これにより、リソースのリクエストを満たすためにサーバー・プッシュが使われたことが裏付けられます。
ORACLE.COM/JAVAMAGAZINE /////////////////// NOVEMBER/DECEMBER 2017
08
//java ee 8 /
HTTP/2に必要なTLS図1を見て、リクエストの方式がHTTPSであることに気づいた方もいらっしゃるかもしれません。これは、すべての主要なブラウザ・ベンダーは、HTTP/2をTransport Layer Security(TLS)でのみ実装することを決めているためで
す。しかし、仕様上はHTTP/2通信にセキュアな接続は必須ではありません。ブラウザ・ベンダーがそう決めている
のは、ユーザーのメリットを考えてのことです。
新しいPushBuilderオブジェクトを利用する際には、注意が必要です。接続がセキュアでない場合、クライアントがサーバー・プッシュをサポートしていない場合、またはクライアントがSETTINGSフレームのSETTINGS_
ENABLE_PUSHパラメータでサーバー・プッシュを無効化するようにリクエストした場合は、newPushBuilder()を呼び出してもnullが返されます。
実際にこの例を試してみたい場合は、GitHubリポジトリからコードをクローンすることができます。
PushBuilderの構造newPushBuilder()の呼出しによって作成される新しいPushBuilderインスタンスは、すべて現在のHttpServletRequestインスタンスがベースとなっています。この新しいインスタンスは、HTTP GETメソッドによって開始されるもので、conditional、range、expect、authorization、referrer以外のすべてのヘッダーは取り除かれます。
PushBuilderは、ビルダー・パターンを実装しています。連鎖メソッド呼出しでインスタンスを変更してから、 push()メソッドを呼び出します。プッシュの前に必要な設定は、リソースのパスのみです。プッシュ・アクションによって非同期ノンブロッキング・リクエストが開始され、その応答が返ってくると、ビルダーを再利用する準備として、パ
スと条件ヘッダーが消去されます。
PushBuilderを実世界のアプリケーションでこのように使用するのがどれほど有用なのかと不思議に思う方も
図1:サーバー・プッシュによって満たされた、リソースのリクエスト
ORACLE.COM/JAVAMAGAZINE /////////////////// NOVEMBER/DECEMBER 2017
09
//java ee 8 /
いらっしゃるかもしれません。そのような疑
問を持つのは当然のことです。先ほどのやや
不自然な例は、説明での使用のみを目的と
したものです。もう少し現実的な使用例は、
アプリケーションでJSFやJSPを使う場合で
す。次は、JSFとの統合と、サーバー・プッシュ
機能をJSPと組み合わせて使う方法について
見ていきます。
JSFとの統合JSF APIは、サーバー・プッシュのメリットを余すことなく活用しています。JSF APIは、リクエストされるWebペー
ジのリソース要件をすべて把握しており、適切にリソースをクライアントにプッシュできるようになっています。
リスト3に、3つのリソースに依存するJSFページの単純な例を示します。
リスト3:<h:outputStylesheet library="css" name="coffee-cup.css"/><h:outputScript library="js" name="coffee-cup.js" target="head"/><h:head> <title>JSF 2.3 Server Push Example</title></h:head><h:body> <h:form> <h:graphicImage library="images" name="coffee-cup.jpg"/> </h:form></h:body></html>
おわかりのように、このJSF Webページには、イメージ、CSSファイル、JavaScriptファイルが必要です。ページが
特に喜ばしいのは、サーバー・プッシュがJSF APIとシームレスに統合されていることです。この統合によって、コードを何も変更することなく、パフォーマンスが強化されているHTTP/2機能を採用できます。
ORACLE.COM/JAVAMAGAZINE /////////////////// NOVEMBER/DECEMBER 2017
10
//java ee 8 /
リクエストされると、サーバー・プッシュによって3つのリソースがすべてクライアントにプッシュされ、その後、送信
されるページが返されます。ChromeのNetworkタブでは、図2のようにこれをはっきりと確認できます。ページのリソースは、JSFのRenderResponsePhaseライフサイクル・フェーズの間にプッシュされます。このプロ
セスで特定された各リソースに対して、ExternalContextImpl.encodeResourceURL()メソッドが呼び出され、リソースの場所が渡されます。すると、ExternalContextに関連付けられたHttpServletRequestインスタンスから、新しくPushBuilderオブジェクトが作成され、リソースがクライアントにプッシュされます。インライン・リソースはすべてクライアントにプッシュされます。このプッシュは、ページがクライアントに送信される前に開始されます。
これを試してみたい方は、GitHubリポジトリからコードをクローンすることができます。
JSPとの統合それでは、JSPの場合はどうでしょうか。残念ながら、JSPではまだサーバー・プッシュのメリットをもたらすアップデ
ートが行われていません。まだJSPを使っている現役のWebアプリケーションも多いことを考えると、これは残念な
ことだと言わざるを得ません。しかし、JSP WebアプリケーションがHTTP 1.1の遅さを受け入れざるを得ないという
わけではありません。カスタムのソリューションではありますが、解決策は存在します。
JSPページはサーブレットに変換され、サーブレットへのリクエストはWebフィルタでインターセプトすることが
できるのはご存知でしょう。そのことを知っていれば、サーバー・プッシュの力を利用したソリューションを構築でき
ます。
Webフィルタを実装することで、Webアプリケーションに対するすべてのリクエストをインターセプトすること
図2:クライアントにプッシュされるJSFインライン・リソース
ORACLE.COM/JAVAMAGAZINE /////////////////// NOVEMBER/DECEMBER 2017
11
//java ee 8 /
ができます。インターセプトを行うフィルタでは、リ
クエストされた各ページのリソースの場所のキャッ
シュが保持されます。以降のリクエストでは、フィル
タが、JSPから変換されたサーブレット(または、チェ
ーンの次のフィルタ)にリクエストを転送する前に、
ページで必要とされるリソースがキャッシュから取
得されてクライアントにプッシュされます。
これが動作する仕組みは、ページが最初にリ
クエストされた際に、ページに必要なリソースが特定される点にあります。ここでは、ページに対する最初のリクエ
ストが行われた直後に、ブラウザが必要なリソースのリクエストを始めるものと仮定しています。リソースに対する
リクエストは、リファラー・ヘッダーを調べて、リファラー・ページの名前とページに対する最初のリクエストの名前
とを照合することで特定されます。これで、ページとリソースの場所のキャッシュが構築されます。
これは、サーバー・プッシュが標準では搭載されていないアプリケーションの一部としてサーバー・プッシュ機
能を組み込む際に効果的なソリューションとなります。実際、Jettyバージョン9のPushCacheFilter Webフィルタに
は、このソリューションが実装されています。
サーバー・プッシュ・フィルタのゲートウェイとなるのは、HttpServletRequestです。 つまり、HttpServletRequestインスタンスがある場所であればどこでも、新しいPushBuilderのインスタンスを使ってリソースのプッシュを始めることができます。
マッピングの実行時検出新しいServlet Mapping APIによって、サーブレットのアクティブ化に使用されるURLマッピングの実行時検出が強
化されています。サーブレットのアクティブ化に使用されたマッピングを正確に把握する必要があるフレームワーク
は、この機能によって恩恵を受けることができる可能性が非常に高いでしょう。たとえば、
file.ext、/path、/path/to/file.extへのリクエストによって、/path/*や*.extというURLパターンを持つサーブレットがアクティブ化されます。
Servlet Mapping APIへの参照は、getHttpServletMapping()メソッドを呼び出して、サーブレットのHttpServletRequestインスタンスから取得します。このAPIでは、次の4つのメソッドが公開されています。
■■ getMatchValue()メソッドは、一致した値を返します。
新しいServlet Mapping APIによって、サーブレットをアクティブ化に使用されるURLマッピングの実行時検出が強化されています。
ORACLE.COM/JAVAMAGAZINE /////////////////// NOVEMBER/DECEMBER 2017
12
//java ee 8 /
■■ getPattern()メソッドは、リクエストに一致したURLパターンを返します。 ■■ getMappingMatch()メソッドは、一致のタイプを表す列挙型を返します。この戻り値は、CONTEXT_ROOT、DEFAULT、EXACT、EXTENSION、IMPLICIT、PATH、UNKNOWNのいずれかとなります。
■■ getServletName()メソッドは、リクエストでアクティブ化されたサーブレットの完全修飾名を返します。 リスト4に、マッピングの実行時検出の例を示します。
リスト4:@WebServlet({"/path/*", "*.ext"})public class ServletMapping extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServlet Response response) throws ServletException, IOException {
HttpServletMapping servletMapping = request.getHttpServletMapping();
response.getWriter() .append("<html><body>") .append("Value Matched: ") .append(servletMapping.getMatchValue()) .append("<br/>") .append("Pattern Used: ") .append(servletMapping.getPattern()) .append("<br/>") .append("Mapping Matched: ") .append(servletMapping.getMappingMatch() .name()) .append("<br/>") .append("Servlet Name:")
ORACLE.COM/JAVAMAGAZINE /////////////////// NOVEMBER/DECEMBER 2017
13
//java ee 8 /
.append(servletMapping.getServletName()) .append("<br/>") .append("</body></html>"); }}
表1にリスト4のコードの出力を示します。この例で返されるサーブレットの名前は、com.readlearncode.servlet4.mapping.ServletMappingです。
Servlet 4.0での他の主なアップデートServlet 4.0リリースでのもっとも重要な追加機能は、おそらくサーバー・プッシュとServlet Mapping APIでしょう。
とはいえ、他の変更点や追加機能に触れないのは怠慢です。
HTTPトレーラや、新しいGenericFilterとHttpFilterのサポートも追加されています。これらの追加機能によって、フィルタの記述が簡略化されます。GenericFilterは、最低限のライフサイクル・メソッドである、initおよびdestroyを実装しています。
さらに、ServletContextには、いくつかの新しいメソッドが追加されています。addJspFile()メソッドは、指定したJSPファイルのサーブレットをサーブレット・コンテキストに追加します。現在のサーブレット・コンテキストのセ
ッション・タイムアウトとデフォルトのリクエスト文字エンコーディングに対するミューテータとアクセッサ・メソッド
も追加されています。
サーブレット・コンテナを構築する際に必須となる最低バージョンが、Java SE 8になりました。この変更に合わ
せて、Listenerインタフェースへのデフォルト・メソッドの追加など、Java 8機能へのアップグレードが行われています。
file.ext /path /path/to/file.extMAPPING MATCHED file path to/file.ext
VALUE MATCHED *.ext /path/* /path/*
PATTERN USED EXTENSION PATH PATH
表1:リスト4の出力
ORACLE.COM/JAVAMAGAZINE /////////////////// NOVEMBER/DECEMBER 2017
14
//java ee 8 /
HttpServletRequestWrapper.isRequestedSessionIdFromUrl()メソッドは非推奨となり、さまざまなメソッドやXML設定について、対応するJavadocにもいくつかの変更や詳しい説明の追加が行われています。
Spring Framework 5.0 リリースされたばかりのSpring Frameworkバージョン5.0では、Tomcat、Jetty、Undertowとともに使用した場
合、HTTP/2のネイティブ・サポートが実現します。Spring Framework 5.1では、Servlet 4.0の完全サポートが予定さ
れています。ただし、サーバー・プッシュの機能は、現在のフレームワークで完全にサポートされています。
まとめサーバー・プッシュとServlet Mapping APIという2つの主要機能は、Servlet APIに追加されたうれしい新機能です。
特に喜ばしいのは、サーバー・プッシュがJSF APIとシームレスに統合されていることです。この統合によって、コード
を何も変更することなく、パフォーマンスが強化されているHTTP/2機能を採用できます。</article>
Alex Theedom(@readlearncode):LinkedIn Learningのインストラクター。『Java EE 8:Only What's New』(Leanpub.com)の著者で、『Professional Java EE Design Patterns』(Wrox Press、2015年)の共著者。ブログ(readlearncode.com)において、Java EEに関する記事を多数執筆している。コンピュータの画面の前にいないときは、さまざまなカンファレンスでJava EE関連トピックの発表をしていることが多い。