原文はこちら。
https://blogs.oracle.com/WebLogicServer/entry/wls_12_2_1_launch
Servletコンテナは、HTTPアップグレードメカニズムを提供します。しかし、Servletコンテナ自体は、アップグレード対象のプロトコルに関する知識を有しません。プロトコル処理はHttpUpgradeHandlerにカプセル化されており、ServletコンテナとHttpUpgradeHandler間のデータの読み込みや書き込みはバイトストリームで行われます。
プロトコルのアップグレードリクエストを受信した場合、Servletは、HttpServletRequest.upgrade()メソッドを呼び出してアップグレードプロセスを開始することができます。このメソッドは、指定されたHttpUpgradeHandlerクラスをインスタンス化します。戻されたHttpUpgradeHandlerインスタンスをさらにカスタマイズすることができます。アプリケーションがクライアントに対して適切なレスポンス準備し、送信します。Servletのサービスメソッドを終了した後、Servletコンテナは、すべてのフィルタ処理を完了し、HttpUpgradeHandlerが処理するよう接続をマークします。そして、HttpUpgradeHandlerのinit()メソッドを呼び出し、プロトコルハンドラがデータストリームへアクセスできるよう、WebConnectionを渡します。
Servletフィルタは最初のHTTPリクエストとレスポンスを処理するだけで、その後の通信に関与しません。つまり、リクエストがアップグレードされると、呼び出されません。HttpUpgradeHandlerは、ノンブロッキングIOを利用して、メッセージを消費、生成することができます。HTTPアップグレードの処理中、開発者はServletInputStream、ServletOutputStreamへのスレッドセーフなアクセスのための責任を負います。アップグレード処理が完了すると、HttpUpgradeHandler.destroyが呼び出されます。
Webコンテナでのノンブロッキングリクエスト処理はWebコンテナのスケーラビリティ改善に対する増大する要望への改善策であり、Webコンテナが同時に処理可能な接続数を増やすことができます。ServletContainerでノンブロッキングIOを使うと、開発者はデータが読み取り可能になった時点、書き込み可能になった時点で操作できるようになります。ノンブロッキングIOはServletとFilter、アップグレード処理における非同期リクエスト処理でのみ利用可能です。それ以外の場合、ServletInputStreamのsetReadListenerを呼び出した場合にはIllegalStateExceptionがスローされなければなりません。
ServerServletでは、サーバがリクエストを受け取り、リクエストの非同期処理を開始して、ReadListenerを登録しています。
ノンブロッキングIOは、ServletおよびFilter、もしくはハンドラのアップグレードにおける非同期リクエスト処理でのみ利用できます。詳しくは、Servlet 3.1の仕様をご覧ください。
[訳注]
原文にはServlet Spec 3.2とありますが、Servlet 3.1の仕様としています。
ユーザーは、異なるパラメータを処理するようにコンストラクタをカスタマイズすることができます。通常、パラメータはServletInputStream、ServletOutputStream、あるいはAsyncContextです。このサンプルでは、それらのすべてを使用して、ReadListenerインタフェースを実装しています。
ServerServlet.javaでは、リクエストを受け取った後にServletが非同期リクエスト処理を開始し、WriteListenerを登録します。
Servletはリクエストからセッションオブジェクトを取得します。sessionIDはそのタイミングで生成されます。request.changeSessionId()が呼び出された後、新たなsessionIDが生成され、セッションオブジェクトの古いsessionIDに置き換わります。
HttpSessionIdListener Implementation
https://blogs.oracle.com/WebLogicServer/entry/wls_12_2_1_launch
Introduction
WebLogic Server 12.2.1ではServlet 3.1仕様の新機能をサポートしています。Servlet 3.1仕様はServlet仕様のメジャーバージョンであり、このバージョンの仕様は、主にノンブロッキングIOとHTTPプロトコルのアップグレード機能をServletContainerに導入し、最新のWebアプリケーション開発に使えるようにしました。ノンブロッキングIOは、Webコンテナのスケーラビリティ改善への増大する要求への対策であるとともに、Webコンテナで同時に処理できる接続数を増やすことができます。ServletコンテナのノンブロッキングIOを使うと、開発者はデータが利用可能になるとデータを読めたり、可能であれば書きこんだりすることができます。また、このバージョンでは、セキュリティと機能強化のためのいくつかのマイナーな変更も導入しました。JSR 340: Java Servlet 3.1 Specification
https://jcp.org/en/jsr/detail?id=340
1 Upgrade Processing
1.1 Description
HTTP/1.1では、general-headerのアップグレードを使うと、サポートされた利用したい追加の通信プロトコルをクライアントは指定することができます。サーバはプロトコルを切り替えることが適当と認める場合には、新しいプロトコルを使って後続の通信を実施します。Servletコンテナは、HTTPアップグレードメカニズムを提供します。しかし、Servletコンテナ自体は、アップグレード対象のプロトコルに関する知識を有しません。プロトコル処理はHttpUpgradeHandlerにカプセル化されており、ServletコンテナとHttpUpgradeHandler間のデータの読み込みや書き込みはバイトストリームで行われます。
プロトコルのアップグレードリクエストを受信した場合、Servletは、HttpServletRequest.upgrade()メソッドを呼び出してアップグレードプロセスを開始することができます。このメソッドは、指定されたHttpUpgradeHandlerクラスをインスタンス化します。戻されたHttpUpgradeHandlerインスタンスをさらにカスタマイズすることができます。アプリケーションがクライアントに対して適切なレスポンス準備し、送信します。Servletのサービスメソッドを終了した後、Servletコンテナは、すべてのフィルタ処理を完了し、HttpUpgradeHandlerが処理するよう接続をマークします。そして、HttpUpgradeHandlerのinit()メソッドを呼び出し、プロトコルハンドラがデータストリームへアクセスできるよう、WebConnectionを渡します。
Servletフィルタは最初のHTTPリクエストとレスポンスを処理するだけで、その後の通信に関与しません。つまり、リクエストがアップグレードされると、呼び出されません。HttpUpgradeHandlerは、ノンブロッキングIOを利用して、メッセージを消費、生成することができます。HTTPアップグレードの処理中、開発者はServletInputStream、ServletOutputStreamへのスレッドセーフなアクセスのための責任を負います。アップグレード処理が完了すると、HttpUpgradeHandler.destroyが呼び出されます。
1.2 Example
この例では、クライアントがサーバに対してリクエストを送信し、サーバがリクエストを受け付け、レスポンスを返し、HttpUpgradeHandler.init()メソッドを呼び出してダミープロトコルを使って通信を継続します。クライアントはハンドシェイクの間、リクエスト・レスポンスヘッダを表示します。Client
クライアントはHTTPアップグレードのリクエストを発行します。この例では、HTTP/1.1ヘッダフィールドをUpgrade: Dummy Protocolに変更しようとしています。さーば はプロトコルアップグレードのリクエストを受け入れるかどうかを判断します。@WebServlet(name = "ClientTest", urlPatterns = {"/"})
public class ClientTest extends HttpServlet {
protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String reqStr = "POST " + contextRoot + "/ServerTest HTTP/1.1" + CRLF;
...
reqStr += "Upgrade: Dummy Protocol" + CRLF;
// Create socket connection to ServerTest
s = new Socket(host, port);
input = s.getInputStream();
output = s.getOutputStream();
// Send request header with data
output.write(reqStr.getBytes());
output.flush();
}
}
Server
ServerTest.javaはリクエストヘッダ中のUpgradeフィールドをチェックします。アップグレードリクエストを受け入れる場合、サーバはProtocolUpgradeHandler(HttpUpgradeHandlerの実装)を呼び出します。クライアントが指定したUpgradeプロトコルをサーバがサポートしない場合、404を返します。ProtocolUpgradeHandlerは、HttpUpgradeHandlerの実装であり、アップグレード要求を処理し、通信プロトコルを切り替えます。サーバはアップグレードヘッダの値をチェックし、そのプロトコルをサポートしているかどうかを判断します。サーバーがリクエストをを受け入れると、101(Switching Protocols)レスポンス中のUpgradeヘッダフィールドを使ってどのプロトコルに切り替わったかを示す必要があります。@WebServlet(name="ServerTest", urlPatterns={"/ServerTest"})
public class ServerTest extends HttpServlet {
protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// Checking request header
if ("Dummy Protocol".equals(request.getHeader("Upgrade"))){
...
ProtocolUpgradeHandler handler = request.upgrade(ProtocolUpgradeHandler.class);
} else {
response.setStatus(400);
...
}
}
...
}
Implementation of HttpUpgradeHandler
init()メソッドは、新しいプロトコル・ヘッダを設定します。新しいプロトコルはその後の通信に使用されます。この例では、ダミーのプロトコルを使用しています。アップグレードプロセスが完了すると、destroy()メソッドが呼び出されます。この例では、プロトコルのアップグレードのハンドシェイクプロセスを示しています。ハンドシェイクプロセスが終わると、後続の通信は、新しいプロトコルを使用します。このメカニズムは、既存のトランスポート層の接続上のアプリケーション層プロトコルのアップグレードにのみ適用されます。この機能は、Java EEプラットフォームプロバイダにとって最も有用です。public class ProtocolUpgradeHandler implements HttpUpgradeHandler {
@Override
public void init(WebConnection wc) {
this.wc = wc;
try {
ServletOutputStream output = wc.getOutputStream();
ServletInputStream input = wc.getInputStream();
Calendar calendar = Calendar.getInstance();
DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
// Reading the data into byte array
input.read(echoData);
// Setting new protocol header
String resStr = "Dummy Protocol/1.0 " + CRLF;
resStr += "Server: Glassfish/ServerTest" + CRLF;
resStr += "Content-Type: text/html" + CRLF;
resStr += "Connection: Upgrade" + CRLF;
resStr += "Date: " + dateFormat.format(calendar.getTime()) +CRLF;
resStr += CRLF;
// Appending data with new protocol
resStr += new String(echoData) + CRLF;
// Sending back to client
...
output.write(resStr.getBytes());
output.flush();
} catch (IOException ex) {
Logger.getLogger(ProtocolUpgradeHandler.class.getName()).log(Level.SEVERE, null, ex);
}
...
}
@Override
public void destroy() {
...
try {
wc.close();
} catch (Exception ex) {
Logger.getLogger(ProtocolUpgradeHandler.class.getName()).log(Level.SEVERE, "Failed to close connection", ex);
}
...
}
}
2 Non-blocking IO
2.1 Description
Webコンテナでのノンブロッキングリクエスト処理はWebコンテナのスケーラビリティ改善に対する増大する要望への改善策であり、Webコンテナが同時に処理可能な接続数を増やすことができます。ServletContainerでノンブロッキングIOを使うと、開発者はデータが読み取り可能になった時点、書き込み可能になった時点で操作できるようになります。ノンブロッキングIOはServletとFilter、アップグレード処理における非同期リクエスト処理でのみ利用可能です。それ以外の場合、ServletInputStreamのsetReadListenerを呼び出した場合にはIllegalStateExceptionがスローされなければなりません。
2.2 Non-Blocking Read Example
Servlet
ServerServletでは、サーバがリクエストを受け取り、リクエストの非同期処理を開始して、ReadListenerを登録しています。
Note:@WebServlet(name = "ServerServlet", urlPatterns = {"/server"}, asyncSupported = true)
public class ServerServlet extends HttpServlet {
.....
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
// async read
final AsyncContext context = request.startAsync();
final ServletInputStream input = request.getInputStream();
final ServletOutputStream output = response.getOutputStream();
input.setReadListener(new ReadListenerImpl(input, output, context));
}
ノンブロッキングIOは、ServletおよびFilter、もしくはハンドラのアップグレードにおける非同期リクエスト処理でのみ利用できます。詳しくは、Servlet 3.1の仕様をご覧ください。
[訳注]
原文にはServlet Spec 3.2とありますが、Servlet 3.1の仕様としています。
Read Listener Implementation
データが入力リクエストストリームから読み込み可能になると、onDataAvailable()メソッドを呼び出します。コンテナはその後、isReady()からtrueが返ってくる場合に限り、read()メソッドを呼び出します。リクエストのすべてのデータが読み込まれたときに、onAllDataRead()メソッドを呼び出します。 onError(Throwable t)メソッドは、リクエストの処理中にエラーや例外が発生する場合に呼び出されます。基礎となるデータストリームがブロックされていない場合、isReady()メソッドはtrueを返します。この時点で、コンテナはonDataAvailable()メソッドを呼び出します。public class ReadListenerImpl implements ReadListener {
private ServletInputStream input;
private ServletOutputStream output;
private AsyncContext context;
private StringBuilder sb = new StringBuilder();
public ReadListenerImpl(ServletInputStream input, ServletOutputStream output, AsyncContext context) {
this.input = input;
this.output = output;
this.context = context;
}
/**
* do when data is available to be read.
*/
@Override
public void onDataAvailable() throws IOException {
while (input.isReady()) {
sb.append((char) input.read());
}
}
/**
* do when all the data has been read.
*/
@Override
public void onAllDataRead() throws IOException {
try {
output.println("ServerServlet has received '" + sb.toString() + "'.");
output.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
context.complete();
}
}
/**
* do when error occurs.
*/
@Override
public void onError(Throwable t) {
context.complete();
t.printStackTrace();
}
ユーザーは、異なるパラメータを処理するようにコンストラクタをカスタマイズすることができます。通常、パラメータはServletInputStream、ServletOutputStream、あるいはAsyncContextです。このサンプルでは、それらのすべてを使用して、ReadListenerインタフェースを実装しています。
2.3 Non-Blocking Write Example
Servlet
ServerServlet.javaでは、リクエストを受け取った後にServletが非同期リクエスト処理を開始し、WriteListenerを登録します。
Write Listener Implementationprotected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
// async write
final AsyncContext context = request.startAsync();
final ServletOutputStream output = response.getOutputStream();
output.setWriteListener(new WriteListenerImpl(output, context));
}
データがレスポンスストリームに書き込めるようになると、onWritePossible()メソッドを呼び出しています。コンテナはその後、isReady()の結果がtrueである場合のみ、writeBytes()メソッドを呼び出します。レスポンスに書き込む間に何らかのエラーや例外が発生した場合には、onError(Throwable t)メソッドを呼び出します。基礎となるデータストリームがブロックされていない場合にisReady()メソッドはtrueを返します。この時点で、コンテナはwriteBytes()メソッドを呼び出します。public class WriteListenerImpl implements WriteListener {
private ServletOutputStream output;
private AsyncContext context;
public WriteListenerImpl(ServletOutputStream output, AsyncContext context) {
this.context = context;
this.output = output;
}
/**
* do when the data is available to be written
*/
@Override
public void onWritePossible() throws IOException {
if (output.isReady()) {
output.println("<p>Server is sending back 5 hello...</p>");
output.flush();
}
for (int i = 1; i <= 5 && output.isReady(); i++) {
output.println("<p>Hello " + i + ".</p>");
output.println("<p>Sleep 3 seconds simulating data blocking.<p>");
output.flush();
// sleep on purpose
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// ignore
}
}
output.println("<p>Sending completes.</p>");
output.flush();
context.complete();
}
/**
* do when error occurs.
*/
@Override
public void onError(Throwable t) {
context.complete();
t.printStackTrace();
}
}
3 SessionID change
3.1 Description
Servlet 3.1仕様にはセッションの固定化を防ぐための新しいインターフェースやメソッドが導入されています。WebLogic ServerのServletContainerはセキュリティ上の理由で、セッションID変更処理が実装されています。3.2 SessionID change Example
このサンプルアプリケーションでは、。SessionIDChangeListenerインターフェースがsessionIdChangedメソッドをオーバーライドしています。このメソッドはセッションのセッションIDの変更通知を受け取ります。SessionIDChangeTestは、javax.servlet.http.HttpServletRequest.changeSessionId()を呼び出してセッションIDの値を変更します。Servlet
@WebServlet(name = "SessionIDChangeServlet", urlPatterns = {"/SessionIDChangeServlet"})
public class SessionIDChangeServlet extends HttpServlet {
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
HttpSession session = request.getSession(true);
try {
StringBuilder sb = new StringBuilder();
sb.append("<h3>Servlet SessionIDChangeTest at " + request.getContextPath() + "</h3><br/>");
sb.append("<p>The current session id is: " + session.getId() + "</p>");
/* Call changeSessionID() method. */
request.changeSessionId();
sb.append("<p>The current session id has been changed, now it is: " + session.getId() + "</p>");
request.setAttribute("message", sb.toString());
request.getRequestDispatcher("response.jsp").forward(request, response);
} finally {
out.close();
}
}
....
}
Servletはリクエストからセッションオブジェクトを取得します。sessionIDはそのタイミングで生成されます。request.changeSessionId()が呼び出された後、新たなsessionIDが生成され、セッションオブジェクトの古いsessionIDに置き換わります。
HttpSessionIdListener Implementation
request.changeSessionId()が呼び出されると、実装されているsessionIdChangedメソッドが起動します。@WebListener
public class SessionIDChangeListener implements HttpSessionIdListener {
@Override
public void sessionIdChanged(HttpSessionEvent event, String oldSessionId) {
System.out.println("[Servlet session-id-change example] Session ID " + oldSessionId + " has been changed");
}
}