顯示具有 RESTful 標籤的文章。 顯示所有文章
顯示具有 RESTful 標籤的文章。 顯示所有文章

2016年8月10日 星期三

How to Use Java to Make an HTTPS Connection without Authentication

注意事項:
    1. TrustModifier 使用上需確認 HTTPS Protocol 是否為 TLS
    2. 本文供參考及學習使用

Catalog

Abstract

過去小編曾撰寫過不同語言的 HTTPS 連線,必須 PASS 掉 HTTPS 中 X509 和 HostName 的狀況並不算少數,這時以 Python 來講只需要增加一個布林值即可,但在 Java 中卻需要實作兩個類別,為了能更簡單的有效的進行程式開發及測試,小編撰寫了一個 TrustModifier,此類別中僅有一個外部可以呼叫的方法,通過此方法可以快速 PASS 掉 X509 及 HostName 驗證

關於 HTTPS 的小知識

在撰寫爬蟲或串接第三方 API 時,不可避免的在某些情況下必須用到 HTTPS 協定,這時如果對方伺服器所提供的是合法的接口,那在實作上只需要直接用 HTTPS 方式去連接即可。但事實並非總是如此,在測試中或使用到內部網路時,會因為伺服器不被信任而造成程式中斷,就小編所知這時我們的選擇,有以下兩種:
  1. 將對方伺服器中的憑證加入信任名單,並設法欺騙己方的伺服器,認為對方的 HostName 是正確的
  2. 在連線過程中無條件 Pass 第一種的兩種檢查
P.S 無論哪一種在傳輸時都仍然有加密,只是目前所要連線的伺服器並未通過驗證可能不安全

TrustModifier.java

import javax.net.ssl.*;
import java.net.URLConnection;
import java.security.cert.*;

/**
 * Created by Cookie on 16/8/9.
 */
public class TrustModifier {
    private static final HostnameVerifier VERIFIER = new AllowHostnameVerifier();
    private static final TrustManager[] MANAGER = new TrustManager[]{new AllowManager()};
    private static final TrustModifier MODIFIER = new TrustModifier("TLS");

    private final SSLSocketFactory factory;

    private TrustModifier(String protocol) {
        try {
            SSLContext ctx = SSLContext.getInstance(protocol);
            ctx.init(null, MANAGER, null);
            factory = ctx.getSocketFactory();
        } catch (Exception e) {
            throw new RuntimeException("Create Trust Modifier Failed.", e);
        }
    }

    private HttpsURLConnection modify(HttpsURLConnection conn) {
        conn.setSSLSocketFactory(factory);
        conn.setHostnameVerifier(VERIFIER);
        return conn;
    }

    /** Call this method and it will modify connection with trust settings. */
    public static URLConnection trust(URLConnection conn) {
        return (conn instanceof HttpsURLConnection)?
                MODIFIER.modify((HttpsURLConnection) conn) : conn;
    }

    private static final class AllowHostnameVerifier implements HostnameVerifier {
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }

    private static final class AllowManager implements X509TrustManager {
        public void checkClientTrusted(X509Certificate[] arg0, String arg1) {}
        public void checkServerTrusted(X509Certificate[] arg0, String arg1) {}
        public X509Certificate[] getAcceptedIssuers() { return null; }
    }
}

SampleMain.java

import java.io.*;
import java.net.*;

/**
 * Created by Cookie on 16/8/9.
 */
public class SampleMain {

    public static void main(String[] args) throws IOException {
        URLConnection conn = new URL("https://74.125.203.94/").openConnection();

        /** Modify connection */
        TrustModifier.trust(conn);

        String inputLine;
        try(InputStreamReader stream = new InputStreamReader(conn.getInputStream());
            BufferedReader in = new BufferedReader(stream)) {
            while ((inputLine = in.readLine()) != null) {
                System.out.println(inputLine);
            }
        }
    }
}

Output

在 Output 中可以看出成功拿到了一段內容並未出現例外,連線成功
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.com/">here</A>.
</BODY></HTML>

2016年7月27日 星期三

Through HTTP Protocol to Learn Web Request

注意事項:
    1. 本文內容主要為小編自己的學習路徑分享
    2. 在使用 Telnet 時須注意換行符號,建議純手動打勿複製貼上
    3. 文章中所使用到的網址可以任意更動

Catalog

Abstract

近日小編在工作上遇到許多連線問題,經過一段時間的摸索意外讓小編學會了使用 Socket 來發送 HTTP Request,而後逐步的學會了使用 telnet 和 s_client 等工具來模擬長連結的 HTTP Request,也認識了網路中的各種連線原來都是利用 Socket 實現,本篇文章將透過一個小測試,來讓各位認識 Web 是如何與 Server 進行溝通請求。

關於 HTTP 的小知識 

扣除早期 0.X 版的 HTTP 目前現行的 HTTP Protocol 多為 1.0 版及 1.1 版,最新版本為 2.0 版非目前主流(本篇內容不使用 2.0 版本),在 1.0 與 1.1 最大的差異在於短連結與長連結,1.0 版預設為短連結(Connection 預設為 close 1.1 則反之)
一個標準的 HTTP Protocol 大致分成 Head 與 Body 兩個部分,中間使用一個換行來進行分隔,範例如下:
POST /2016/06/extract-transform-load.html HTTP/1.1
Host: tsai-cookie.blogspot.tw
User-Agent: User Defient/1.0
Content-Type: text/plain; charset=utf-8

<.... Your Data ....>

紅:Method
綠:Path
藍:HTTP Version
橘:Head
紫:Body(許多地方文章稱為 Entity)
P.S 在實驗中,此 POST HTTP Request 伺服器並不支援,僅供參考用。
並不是所有 HTTP Request 都有 body 最常見的四種 GET, POST, DELETE 和 PUT 中,只有 POST 和 PUT 可以攜帶 Body,而沒有攜帶 Body 的 Request 就不用 Content-Type 和 Body,反之則需要。
無 Body 的 Request,範例如下:
GET /2016/06/extract-transform-load.html HTTP/1.1
Host: tsai-cookie.blogspot.tw
User-Agent: User Defient/1.0

Experiment

接下來小編將帶各位做一個簡單的實驗,利用 telnet 及 s_client 向 www.google.com.tw 發送一個手製的 HTTP Request,首先,我們需要一個完整的 HTTP Reuqest,內容如下:
GET / HTTP/1.1
Host: www.google.com.tw
User-Agent: CookieTsai
Connection: close

P.S Connection: close 目的為告訴 Server 做完後結束連線,如不要這行可以連續發送數次。
如您使用的作業系統為 Windows,想要做此實驗須先手動打開 telnet 或安裝 cygwin,本文未提供方法造成不便請見諒。

Telnet 

於 命令提示字元 或 終端機 使用指令
$ telnet www.google.com.tw 80
Trying 64.233.187.94...
Connected to www.google.com.tw.
Escape character is '^]'.
進入後輸入 Request(紅色)並按下 Entry(按 1~2 次需產生一行空白行)即會收到 Server 回應(藍色)
內容如下:
GET / HTTP/1.1
Host: www.google.com.tw
User-Agent: CookieTsai
Connection: close

HTTP/1.1 200 OK
Date: Wed, 27 Jul 2016 11:24:19 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=Big5
P3P: CP="This is not a P3P policy! See https://www.google.com/support/accounts/answer/151657?hl=en for more info."
Server: gws
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
Set-Cookie: NID=83=xuZXlGKYIFhYU7tlUD9YzxAcNuOxvrFSNa191FEEdOxB0s8xpW3W-u8t4BQnURrmtGuDVbgAYzq6Ed9MtCwqPdfGndeJ6GieOpNkBF5qaztXmm5H3o5CQ-FukayW1fhH; expires=Thu, 26-Jan-2017 11:24:19 GMT; path=/; domain=.google.com.tw; HttpOnly
Accept-Ranges: none
Vary: Accept-Encoding
Transfer-Encoding: chunked

28ba...(略)

Openssl's s_client 

既然使用 telnet 就可以發送了,為何需要使用 openssl 的 s_client 呢?
許多網站為了資訊安全會使用 HTTPS,這並不是指有一種 Protocol 是 HTTPS,而是一般的 HTTP Protocol + SSL,另一種說法是 HTTP Protocol 在傳送時加上 SSL 的加密方法。這時候單純的 telnet 無法提供 SSL 加解密,因此,要利用 openssl 提供的 s_client 來發送請球,內容大致如下。
於 命令提示字元 或 終端機 使用指令
$ openssl s_client -connect www.google.com.tw:443
CONNECTED(00000003)
depth=2 /C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
verify error:num=20:unable to get local issuer certificate
verify return:0
---
Certificate chain
 0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=google.com
   i:/C=US/O=Google Inc/CN=Google Internet Authority G2
 1 s:/C=US/O=Google Inc/CN=Google Internet Authority G2
   i:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
 2 s:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
   i:/C=US/O=Equifax/OU=Equifax Secure Certificate Authority
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIgHDCCHwSgAwIBAgIIEAtlXh5wxiwwDQYJKoZIhvcNAQELBQAwSTELMAkGA1UE
BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl
cm5ldCBBdXRob3JpdHkgRzIwHhcNMTYwNzEzMTMxNjMyWhcNMTYxMDA1MTMxNjAw
...
...(中間省略非常長)...
...
Q57MUEhLPbQnNPaP5KXQfMB+9GbXrCGwJzX+Uue0T99zo/UuKW1pJUDM8jDhgMP3
Io5fc9LqwWuWDARjvODD6L7ZefN192ovI41+3yJ6ImOUvPJR31M4ZqiMT2MSJ1NT
k2w8H4X3z0pLvtlRFyDoq5TwkU8r6QnBQFD/rC6tAqvx7WGLc2nRXy2bgcKItwC/
uO9p+m2wqZJPPCQOnE0m6A==
-----END CERTIFICATE-----
subject=/C=US/ST=California/L=Mountain View/O=Google Inc/CN=google.com
issuer=/C=US/O=Google Inc/CN=Google Internet Authority G2
---
No client certificate CA names sent
---
SSL handshake has read 10308 bytes and written 456 bytes
---
New, TLSv1/SSLv3, Cipher is AES128-SHA
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1
    Cipher    : AES128-SHA
    Session-ID: 13F7455348699FFF98FB6D11865EDDAEB3F904D6AC727041CA4F3857A0E97226
    Session-ID-ctx:
    Master-Key: F74606075CAC3B709F39CA9F2C699FCEA892C16F5E58AF0EC453433F74B0E7F7FD10890168C19E8EEAB1727F5CD4A478
    Key-Arg   : None
    Start Time: 1469623117
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)
---
進入後輸入 Request(紅色)並按下 Entry(按 1~2 次需產生一行空白行)即會收到 Server 回應(藍色)
內容如下:
GET / HTTP/1.1
Host: www.google.com.tw
User-Agent: CookieTsai
Connection: close

HTTP/1.1 200 OK
Date: Wed, 27 Jul 2016 12:39:25 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=Big5
P3P: CP="This is not a P3P policy! See https://www.google.com/support/accounts/answer/151657?hl=en for more info."
Server: gws
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
Set-Cookie: NID=83=kdPJl42ANhiQji2fmjEO4h9aOjwp6ZWcBvXIREOnOVYBfWlS7PMjwn1w8ZTlc_gUjbXvQvnMGkJkKnsZGLy7CcDVO0s40Q7mofOTY4t2Uvt_kVSD7KumameS8PYDvLtj; expires=Thu, 26-Jan-2017 12:39:25 GMT; path=/; domain=.google.com.tw; HttpOnly
Alternate-Protocol: 443:quic
Alt-Svc: quic=":443"; ma=2592000; v="36,35,34,33,32,31,30,29,28,27,26,25"
Accept-Ranges: none
Vary: Accept-Encoding
Connection: close

...(略)

2016年4月15日 星期五

RESTful Api 執行多個 Resource 及控制取回的資料

注意事項:
    1. 文章內容皆為自己臨時撰寫並且已經過實測,僅提供了部分程式碼並不能直接運行 
    2. 文中有使用另一篇文章內容可參考 Trigger and Masker
    3. 僅供參考

目錄

前言

今天恰好在某社群上看到一篇關於 『RESTful API如何一次完成多個相依請求』的內容,想想小編也是做這方面的工作。索性將幾個曾經遇到的問題,寫成自己用過的解決方案提供給大家作為參考使用。

問題

本篇文章中有 Resource1, Resource2, Resource3 三支 Api,其中目的,如下:
  1. Resource 1:提供查詢功能可回應 Data1 及 Data2 兩種內容。
  2. Resource 2:提供創建(例如:創建使用者)
  3. Resource 3:提供創建(例如:創建團體)
P.S. 以下都用 R1, R2, R3 作為簡稱。
情境描述,如下:
  1. 通過 R1 希望可以取得多種回應結果,例如:使用者的基本資料或所擁有的權限,選擇部分或全部取得。
  2. 執行 R2 的某些情況下可能有需要同時執行 R3,例如:在沒有團體時創建使用者,將同時創建一個團體。
P.S. 以上皆為隨意假設需以真實情況異動,請勿直接取用。

解決方法

  1. 在情境中選擇使用一個 Tigger Value(一種二進制整數)做為開關,當請求發送上來後可以決定需要的資料。
  2. 在執行 R2 時多帶一個參數決定是否要額外執行 R3。(有將 R2 的結果帶入 R3)
P.S. Trigger Value 的製作與應用請參考 Trigger and Masker

Smaple Code

Resource Api

/**
 * Created by Cookie on 16/4/15.
 */
@Singleton
@Produces(MediaType.APPLICATION_JSON)
@Path("sample")
public class SampleApi {

    private static final SingleService singleService = new SampleService();

    @GET
    @Path("resource1")
    public SingleResponse getResource1(@QueryParam("tv") Integer triggerValue) {
        SingleResponse response = new SingleResponse();

        Trigger trigger = SampleMasker.newTrigger(triggerValue);
        if (trigger.isOn(SampleMasker.OPTION_01)) {
            // 為證明有執行在回應中放入點資料
            response.put("data1", "value1");
        }
        if (trigger.isOn(SampleMasker.OPTION_02)) {
            // 為證明有執行在回應中放入點資料
            response.put("data2", "value2");
        }
        // 回傳 Response
        return response.ok();
    }

    @POST
    @Path("resource2")
    public SingleResponse getResource2(@QueryParam("make5") Boolean make5, 
                                       SingleRequest request) {

        // 執行 Resource 2
        SingleResponse response = AppUtils.callback(singleService);
        // 為證明有執行在回應中放入點資料
        response.put("R2", "ok");

        // Resource 2 執行成功且請求有需要完成 Resource 3 則執行 Resource 3
        if (response.isSuccess() && BooleanUtils.isTrue(make5)) {

            // 取出 Resource 2 執行後獲得的資料
            Map<String, Object> map = response.getDataObj();

            // 將 Resource 2 執行後的內容傳送給 Resource 3
            SingleResponse response3 = getResource3(new SingleRequest().putAll(map));

            // TODO 可自行決定是否將 Resource 3 與 Resource 2 的 Response 合併
            // 為證明有執行在回應中放入點資料
            response.put("R3", "ok");
        }

        // 回傳 Response
        return response;
    }

    @POST
    @Path("resource3")
    public SingleResponse getResource3(SingleRequest request) {
        // 執行 Resource 3 並回傳 Response
        return AppUtils.callback(singleService);
    }
}

Resource 1 測試與輸出

// 僅取回 第一種資料
$ curl -s http://127.0.0.1:9090/api/sample/resource1?tv=1 | jq .
{
  "data": {
    "data1": "value1"
  },
  "code": 0,
  "success": true
}
// 僅取回 第二種資料
$ curl -s http://127.0.0.1:9090/api/sample/resource1?tv=2 | jq .
{
  "data": {
    "data2": "value2"
  },
  "code": 0,
  "success": true
}
// 同時取回 第一及第二種資料
$ curl -s http://127.0.0.1:9090/api/sample/resource1?tv=3 | jq .
{
  "data": {
    "data1": "value1",
    "data2": "value2"
  },
  "code": 0,
  "success": true
}

Resource 2 測試與輸出

// 執行 Resource 2 並要求執行 Resource 3
$ curl -s -X POST http://127.0.0.1:9090/api/sample/resource2?make5=true -d "{}" | jq .
{
  "data": {
    "R2": "ok",
    "R3": "ok"
  },
  "code": 0,
  "success": true
}

2015年12月16日 星期三

Grizzly 2.0: HttpServer API. Asynchronous HTTP Server - Part I

注意事項:
    1. 本文轉載於 Mytec Blog for oleksiys
    2. 建議先瞭解一下 Grizzly IOStrategies 的第一種 Worker-thread 模式
    3. 僅供參考查閱使用
In my previous blog entry I described basic Grizzly HttpServer API abstractions and offered a couple of samples to show how one can implement light-weight Servlet-like web application.
Here I'll try to show how we can process HTTP requests asynchronously within HttpHandler, in other words implement asynchronous HTTP application.
What do we mean by "asynchronous"?
Normally HttpServer has a service thread-pool, whose threads are used for HTTP requests processing, which includes following steps:
  1. parse HTTP request;
  2. execute processing logic by calling HttpHandler.handle(Request, Response);
  3. flush HTTP response;
  4. return service thread to a thread-pool.
Normally the steps above are executed sequentially in a service thread. Using "asynchronous" feature it's possible to delegate execution of steps 2 and 3 to a custom thread, which will let us release the service thread faster.
Why would we want to do that?
As it was said above, the service thread-pool instance is shared between all the HttpHandlers registered on HttpServer. Assume we have application (HttpHandler) "A", which executes a long lasting task (say a SQL query on pretty busy DB server), and application "B", which serves static resources. It's easy to imagine the situation, when couple of application "A" clients block all the service threads by waiting for response from DB server. The main problem is that clients of application "B", which is pretty light-weight, can not be served at the same time because there are no available service threads. So it might be a good idea to isolate these applications by executing application "A" logic in the dedicated thread pool, so service threads won't be blocked.
Ok, let's do some coding and make sure the issue we've just described is real.
HttpServer httpServer = new HttpServer();

NetworkListener networkListener = new NetworkListener("sample-listener", "127.0.0.1", 18888);

// Configure NetworkListener thread pool to have just one thread,
// so it would be easier to reproduce the problem
ThreadPoolConfig threadPoolConfig = ThreadPoolConfig
        .defaultConfig()
        .setCorePoolSize(1)
        .setMaxPoolSize(1);

networkListener.getTransport().setWorkerThreadPoolConfig(threadPoolConfig);

httpServer.addListener(networkListener);

httpServer.getServerConfiguration().addHttpHandler(new HttpHandler() {
    @Override
    public void service(Request request, Response response) throws Exception {
        response.setContentType("text/plain");
        response.getWriter().write("Simple task is done!");
    }
}, "/simple");

httpServer.getServerConfiguration().addHttpHandler(new HttpHandler() {
    @Override
    public void service(Request request, Response response) throws Exception {
        response.setContentType("text/plain");
        // Simulate long lasting task
        Thread.sleep(10000);
        response.getWriter().write("Complex task is done!");
    }
}, "/complex");

try {
    server.start();
    System.out.println("Press any key to stop the server...");
    System.in.read();
} catch (Exception e) {
    System.err.println(e);
}
In the sample above we create, initialize and run HTTP server, which has 2 applications (HttpHandlers) registered: "simple" and "complex". To simulate long-lasting task in the "complex" application we're just causing the current thread to sleep for 10 seconds.
Now if you try to call "simple" application from you Web browser using URL: http://localhost:18888/simple - you see the response immediately. However, if you try to call "complex" application http://localhost:18888/complex - you'll see response in 10 seconds. That's fine. But try to call "complex" application first and then quickly, in different tab, call the "simple" application, do you see the response immediately? Probably not. You'll see the response right after "complex" application execution completed. The sad thing here is that service thread, which is executing "complex" operation is idle (the same situation is when you wait for SQL query result), so CPU is doing nothing, but still we're not able to process another HTTP request.
How we can rework the "complex" application to execute its task in custom thread pool? Normally application (HttpHandler) logic is encapsulated within HttpHandler.service(Request, Response) method, once we exit this method, Grizzly finishes and flushes HTTP response. So coming back to the service thread processing steps:
  1. parse HTTP request;
  2. execute processing logic by calling HttpHandler.handle(Request, Response);
  3. flush HTTP response;
  4. return service thread to a thread-pool.
we see that it wouldn't be enough just to delegate HTTP request processing to a custom thread on step 2, because on step 3 Grizzly will automatically flush HTTP response back to client at the state it currently is. We need a way to instruct Grizzly to not do 3 automatically on the service thread, instead we want to be able to perform this step ourselves once asynchronous processing is complete.
Using Grizzly HttpServer API it could be achieved following way:
  • HttpResponse.suspend(...) to instruct Grizzly to not flush HTTP response in the service thread;
  • HttpResponse.resume() to finish HTTP request processing and flush response back to client.
So asynchronous version of the "complex" application (HttpHandler) will look like:
httpServer.getServerConfiguration().addHttpHandler(new HttpHandler() {
    final ExecutorService complexAppExecutorService =
        GrizzlyExecutorService.createInstance(
            ThreadPoolConfig.defaultConfig()
            .copy()
            .setCorePoolSize(5)
            .setMaxPoolSize(5));
            
    @Override
    public void service(final Request request, final Response response) throws Exception {
                
        response.suspend(); // Instruct Grizzly to not flush response, once we exit the service(...) method 
                
        complexAppExecutorService.execute(new Runnable() {   // Execute long-lasting task in the custom thread
            public void run() {
                try {
                    response.setContentType("text/plain");
                    // Simulate long lasting task
                    Thread.sleep(10000);
                    response.getWriter().write("Complex task is done!");
                } catch (Exception e) {
                    response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
                } finally {
                    response.resume();  // Finishing HTTP request processing and flushing the response to the client
                }
            }
        });
    }
}, "/complex");
  • As you might have noticed, "complex" application uses Grizzly ExecutorService implementation. This is the preferred approach, however you can still use own ExecutorService.
The three most important steps in the code above are marked red:
  1. Suspend HTTP response processing: response.suspend()
  2. Delegating task to the custom thread pool: complexAppExecutorService.execute(...)
  3. Resuming HTTP response processing: response.resume()
Now, using your browser, you can make sure "simple" and "complex" applications are not affecting each other, and the "simple" application works just fine when the "complex" application is busy.

筆記

實作中筆者刻意將 thread pool 設定為僅有一個 Worker 的情況下運行。
在此案例中,如果我們先行呼叫 complex 而後在呼叫 simple 會發現,simple 需要等 complex 做完才可以運行。
在這樣的狀況下會發生一個問題,也就是當一些複雜的 API 被呼叫時可能造成一些簡單的 Request 在排隊等待。
緊接著筆者透過另起一群 Thread pool 專門處理 complex 這樣即使只有一個 Worker 也不至會造成排隊的問題。
個人對此文章的理解為:建議耗時長的API應與耗時短的API,分別處理這樣可以使伺服器運作得更為順利

Best Practices

注意事項:
    1. 本文來源為 Grizzly 官方網站文件
    2. 請以官方文件為主,此文僅供參考使用
When developing a network application, we usually wonder how we can optimize it. How should the worker thread pool be sized? Which I/O strategy to employ?
There is no general answer for that question, but we’ll try to provide some tips.
  • IOStrategy
    In the IOStrategy section, we introduced different Grizzly IOStrategies.
    By default, Grizzly Transports use the worker-thread IOStrategy, which is reliable for any possible usecase. However, if the application processing logic doesn’t involve any blocking I/O operations, the same-thread IOStrategy can be used. For these cases, the same-thread strategy will yield better performance as there are no thread context switches.
    For example, if we implement general HTTP Servlet container, we can’t be sure about nature of specific Servlets developers may have. In this case it’s safer to use the worker-thread IOStrategy. However, if application uses the Grizzly’s HttpServer and HttpHandler, which leverages NIO streams, then the same-thread strategy could be used to optimize processing time and resource consumption;
  • Selector runners count
    The Grizzly runtime will automatically set the SelectorRunner count value equal to Runtime.getRuntime().availableProcessors(). Depending on the usecase, developers may change this value to better suit their needs.
    Scott Oaks, from the Glassfish performance team, suggests that there should be one SelectorRunner for every 1-4 cores on your machine; no more than that;
  • Worker thread pool
    In the Configuration threadpool-config section, the different thread pool implementations, and their pros and cons, were discussed.
    All IOStrategies, except the same-thread IOStrategy, use worker threads to process IOEvents which occur on Connections. A common question is how many worker threads will be needed by an application?
    In his blog, Scott suggests How many is “just enough”? It depends, of course – in a case where HTTP requests don’t use any external resource and are hence CPU bound, you want only as many HTTP request processing threads as you have CPUs on the machine. But if the HTTP request makes a database call (even indirectly, like by using a JPA entity), the request will block while waiting for the database, and you could profitably run another thread. So this takes some trial and error, but start with the same number of threads as you have CPU and increase them until you no longer see an improvement in throughput.
    Translating this to the general, non HTTP usecase: If IOEvent processing includes blocking I/O operation(s), which will make thread block doing nothing for some time (i.e, waiting for a result from a peer), it’s best to have more worker threads to not starve other request processing. For simpler application processes, the fewer threads, the better.

筆記

  • Grizzly 建議的調整方向可區分成三個 IOStrategy, Selector 和 Worker thread pool。
    • IOStrategy 調整伺服器運作模式,包含 Worker-thread(預設), Same-thread, Dynamic 和 Leader-follower。
    • Selector 建議配合電腦核心數去設定,每 1 ~ 4個核心對應 1 個 Selector(此預設值為 Runtime.getRuntime().availableProcessors() 的數值)
    • 官方提供建議 thread pool 設定為夠用就好並非越大就越快,此外 Worker thread pool 必須配合 IOStrategy 決定是否有效。如:Same-thread 模式中似乎並沒有運用到 Worker。