netty高級篇(3)-HTTP協議開發

1、HTTP協議簡介

應用層協議http,發展至今已是http2.0了,擁有如下特色:html

(1) CS模式的協議java

(2) 簡單 - 只須要服務URL,攜帶必要的請求參數或者消息體web

(3) 靈活 - 任意類型,傳輸內容類型由HTTP消息頭中的Content-Type加以標記json

(4) 無狀態 - 必須藉助額外手段,好比session或者cookie來保持狀態bootstrap

1.1 HTTP請求消息(HttpRequest)

客戶端發送一個HTTP請求到服務器的請求消息包括如下格式:請求行(request line)、請求頭部(header)、空行和請求數據四個部分組成,下圖給出了請求報文的通常格式。瀏覽器

舉個例子:緩存

GET /hello.txt HTTP/1.1
User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
Host: www.example.com
Accept-Language: en, mi

請求方法:

根據HTTP標準,HTTP請求可使用多種請求方法。安全

HTTP1.0定義了三種請求方法: GET, POST 和 HEAD方法。服務器

HTTP1.1新增了五種請求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。cookie

序號 方法 描述
1 GET 請求指定的頁面信息,並返回實體主體。
2 HEAD 相似於get請求,只不過返回的響應中沒有具體的內容,用於獲取報頭
3 POST 向指定資源提交數據進行處理請求(例如提交表單或者上傳文件)。數據被包含在請求體中。POST請求可能會致使新的資源的創建和/或已有資源的修改。
4 PUT 從客戶端向服務器傳送的數據取代指定的文檔的內容。
5 DELETE 請求服務器刪除指定的頁面。
6 CONNECT HTTP/1.1協議中預留給可以將鏈接改成管道方式的代理服務器。
7 OPTIONS 容許客戶端查看服務器的性能。
8 TRACE 回顯服務器收到的請求,主要用於測試或診斷。

GET方法:參數在請求行,不安全且有必定限制

POST方法:要求在服務器接受後面的數據,經常使用於提交表單。

通常GET用於獲取/查詢信息,而POST通常用於建立,更新信息。兩者主要區別以下:

(1) 根據HTTP規範,GET用於獲取,應該是安全和冪等的,而POST則表示可能改變服務器上的資源;

(2) GET請求數據會附在URL上,即請求行中,以"?"分隔URL和傳輸數據,多個參數用&鏈接;而POST會把數據放在HTTP消息的報體中,地址欄中沒有

(3) 傳輸數據的大小不一樣,特定瀏覽器有限制,例如IE對URL限制是2083字節,POST理論上沒有限制

(4) POST更安全,使用GET還有可能受到Cross-site request forgery攻擊等等。

部分請求頭部說明:

Header 解釋 示例
Accept 指定客戶端可以接收的內容類型 Accept: text/plain, text/html
Accept-Charset 瀏覽器能夠接受的字符編碼集。 Accept-Charset: iso-8859-5
Accept-Encoding 指定瀏覽器能夠支持的web服務器返回內容壓縮編碼類型。 Accept-Encoding: compress, gzip
Accept-Language 瀏覽器可接受的語言 Accept-Language: en,zh
Accept-Ranges 能夠請求網頁實體的一個或者多個子範圍字段 Accept-Ranges: bytes
Authorization HTTP受權的受權證書 Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Cache-Control 指定請求和響應遵循的緩存機制 Cache-Control: no-cache
Connection 表示是否須要持久鏈接。(HTTP 1.1默認進行持久鏈接) Connection: close
Cookie HTTP請求發送時,會把保存在該請求域名下的全部cookie值一塊兒發送給web服務器。 Cookie: $Version=1; Skin=new;
Content-Length 請求的內容長度 Content-Length: 348
Content-Type 請求的與實體對應的MIME信息 Content-Type: application/x-www-form-urlencoded
Date 請求發送的日期和時間 Date: Tue, 15 Nov 2010 08:12:31 GMT
Expect 請求的特定的服務器行爲 Expect: 100-continue
From 發出請求的用戶的Email From: user@email.com
Host 指定請求的服務器的域名和端口號 Host: www.zcmhi.com
If-Match 只有請求內容與實體相匹配纔有效 If-Match: 「737060cd8c284d8af7ad3082f209582d」
If-Modified-Since 若是請求的部分在指定時間以後被修改則請求成功,未被修改則返回304代碼 If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT
If-None-Match 若是內容未改變返回304代碼,參數爲服務器先前發送的Etag,與服務器迴應的Etag比較判斷是否改變 If-None-Match: 「737060cd8c284d8af7ad3082f209582d」
If-Range 若是實體未改變,服務器發送客戶端丟失的部分,不然發送整個實體。參數也爲Etag If-Range: 「737060cd8c284d8af7ad3082f209582d」
If-Unmodified-Since 只在實體在指定時間以後未被修改才請求成功 If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT
Max-Forwards 限制信息經過代理和網關傳送的時間 Max-Forwards: 10
Pragma 用來包含實現特定的指令 Pragma: no-cache
Proxy-Authorization 鏈接到代理的受權證書 Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Range 只請求實體的一部分,指定範圍 Range: bytes=500-999
Referer 先前網頁的地址,當前請求網頁緊隨其後,即來路 Referer: http://www.zcmhi.com/archives/71.html
TE 客戶端願意接受的傳輸編碼,並通知服務器接受接受尾加頭信息 TE: trailers,deflate;q=0.5
Upgrade 向服務器指定某種傳輸協議以便服務器進行轉換(若是支持) Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11
User-Agent User-Agent的內容包含發出請求的用戶信息 User-Agent: Mozilla/5.0 (Linux; X11)
Via 通知中間網關或代理服務器地址,通訊協議 Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)
Warning 關於消息實體的警告信息 Warn: 199 Miscellaneous warning

1.2 HTTP響應消息

HTTP響應也由四個部分組成,分別是:狀態行、消息報頭、空行和響應正文。

httpmessage

HTTP狀態碼

當瀏覽者訪問一個網頁時,瀏覽者的瀏覽器會向網頁所在服務器發出請求。當瀏覽器接收並顯示網頁前,此網頁所在的服務器會返回一個包含HTTP狀態碼的信息頭(server header)用以響應瀏覽器的請求。

HTTP狀態碼的英文爲HTTP Status Code。

下面是常見的HTTP狀態碼:

  • 200 - 請求成功
  • 301 - 資源(網頁等)被永久轉移到其它URL
  • 404 - 請求的資源(網頁等)不存在
  • 500 - 內部服務器錯誤

HTTP狀態碼分類

HTTP狀態碼由三個十進制數字組成,第一個十進制數字定義了狀態碼的類型,後兩個數字沒有分類的做用。HTTP狀態碼共分爲5種類型:

HTTP狀態碼分類
分類 分類描述
1** 信息,服務器收到請求,須要請求者繼續執行操做
2** 成功,操做被成功接收並處理
3** 重定向,須要進一步的操做以完成請求
4** 客戶端錯誤,請求包含語法錯誤或沒法完成請求
5** 服務器錯誤,服務器在處理請求的過程當中發生了錯誤

HTTP狀態碼列表:

HTTP狀態碼列表
狀態碼 狀態碼英文名稱 中文描述
100 Continue 繼續。客戶端應繼續其請求
101 Switching Protocols 切換協議。服務器根據客戶端的請求切換協議。只能切換到更高級的協議,例如,切換到HTTP的新版本協議
 
200 OK 請求成功。通常用於GET與POST請求
201 Created 已建立。成功請求並建立了新的資源
202 Accepted 已接受。已經接受請求,但未處理完成
203 Non-Authoritative Information 非受權信息。請求成功。但返回的meta信息不在原始的服務器,而是一個副本
204 No Content 無內容。服務器成功處理,但未返回內容。在未更新網頁的狀況下,可確保瀏覽器繼續顯示當前文檔
205 Reset Content 重置內容。服務器處理成功,用戶終端(例如:瀏覽器)應重置文檔視圖。可經過此返回碼清除瀏覽器的表單域
206 Partial Content 部份內容。服務器成功處理了部分GET請求
 
300 Multiple Choices 多種選擇。請求的資源可包括多個位置,相應可返回一個資源特徵與地址的列表用於用戶終端(例如:瀏覽器)選擇
301 Moved Permanently 永久移動。請求的資源已被永久的移動到新URI,返回信息會包括新的URI,瀏覽器會自動定向到新URI。從此任何新的請求都應使用新的URI代替
302 Found 臨時移動。與301相似。但資源只是臨時被移動。客戶端應繼續使用原有URI
303 See Other 查看其它地址。與301相似。使用GET和POST請求查看
304 Not Modified 未修改。所請求的資源未修改,服務器返回此狀態碼時,不會返回任何資源。客戶端一般會緩存訪問過的資源,經過提供一個頭信息指出客戶端但願只返回在指定日期以後修改的資源
305 Use Proxy 使用代理。所請求的資源必須經過代理訪問
306 Unused 已經被廢棄的HTTP狀態碼
307 Temporary Redirect 臨時重定向。與302相似。使用GET請求重定向
 
400 Bad Request 客戶端請求的語法錯誤,服務器沒法理解
401 Unauthorized 請求要求用戶的身份認證
402 Payment Required 保留,未來使用
403 Forbidden 服務器理解請求客戶端的請求,可是拒絕執行此請求
404 Not Found 服務器沒法根據客戶端的請求找到資源(網頁)。經過此代碼,網站設計人員可設置"您所請求的資源沒法找到"的個性頁面
405 Method Not Allowed 客戶端請求中的方法被禁止
406 Not Acceptable 服務器沒法根據客戶端請求的內容特性完成請求
407 Proxy Authentication Required 請求要求代理的身份認證,與401相似,但請求者應當使用代理進行受權
408 Request Time-out 服務器等待客戶端發送的請求時間過長,超時
409 Conflict 服務器完成客戶端的PUT請求是可能返回此代碼,服務器處理請求時發生了衝突
410 Gone 客戶端請求的資源已經不存在。410不一樣於404,若是資源之前有如今被永久刪除了可以使用410代碼,網站設計人員可經過301代碼指定資源的新位置
411 Length Required 服務器沒法處理客戶端發送的不帶Content-Length的請求信息
412 Precondition Failed 客戶端請求信息的先決條件錯誤
413 Request Entity Too Large 因爲請求的實體過大,服務器沒法處理,所以拒絕請求。爲防止客戶端的連續請求,服務器可能會關閉鏈接。若是隻是服務器暫時沒法處理,則會包含一個Retry-After的響應信息
414 Request-URI Too Large 請求的URI過長(URI一般爲網址),服務器沒法處理
415 Unsupported Media Type 服務器沒法處理請求附帶的媒體格式
416 Requested range not satisfiable 客戶端請求的範圍無效
417 Expectation Failed 服務器沒法知足Expect的請求頭信息
 
500 Internal Server Error 服務器內部錯誤,沒法完成請求
501 Not Implemented 服務器不支持請求的功能,沒法完成請求
502 Bad Gateway 充當網關或代理的服務器,從遠端服務器接收到了一個無效的請求
503 Service Unavailable 因爲超載或系統維護,服務器暫時的沒法處理客戶端的請求。延時的長度可包含在服務器的Retry-After頭信息中
504 Gateway Time-out 充當網關或代理的服務器,未及時從遠端服務器獲取請求
505 HTTP Version not supported 服務器不支持請求

 

響應頭信息

Header 解釋 示例
Accept-Ranges 代表服務器是否支持指定範圍請求及哪一種類型的分段請求 Accept-Ranges: bytes
Age 從原始服務器到代理緩存造成的估算時間(以秒計,非負) Age: 12
Allow 對某網絡資源的有效的請求行爲,不容許則返回405 Allow: GET, HEAD
Cache-Control 告訴全部的緩存機制是否能夠緩存及哪一種類型 Cache-Control: no-cache
Content-Encoding web服務器支持的返回內容壓縮編碼類型。 Content-Encoding: gzip
Content-Language 響應體的語言 Content-Language: en,zh
Content-Length 響應體的長度 Content-Length: 348
Content-Location 請求資源可替代的備用的另外一地址 Content-Location: /index.htm
Content-MD5 返回資源的MD5校驗值 Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ==
Content-Range 在整個返回體中本部分的字節位置 Content-Range: bytes 21010-47021/47022
Content-Type 返回內容的MIME類型 Content-Type: text/html; charset=utf-8
Date 原始服務器消息發出的時間 Date: Tue, 15 Nov 2010 08:12:31 GMT
ETag 請求變量的實體標籤的當前值 ETag: 「737060cd8c284d8af7ad3082f209582d」
Expires 響應過時的日期和時間 Expires: Thu, 01 Dec 2010 16:00:00 GMT
Last-Modified 請求資源的最後修改時間 Last-Modified: Tue, 15 Nov 2010 12:45:26 GMT
Location 用來重定向接收方到非請求URL的位置來完成請求或標識新的資源 Location: http://www.zcmhi.com/archives/94.html
Pragma 包括實現特定的指令,它可應用到響應鏈上的任何接收方 Pragma: no-cache
Proxy-Authenticate 它指出認證方案和可應用到代理的該URL上的參數 Proxy-Authenticate: Basic
refresh 應用於重定向或一個新的資源被創造,在5秒以後重定向(由網景提出,被大部分瀏覽器支持)
 

 

Refresh: 5; url=
http://www.zcmhi.com/archives/94.html
Retry-After 若是實體暫時不可取,通知客戶端在指定時間以後再次嘗試 Retry-After: 120
Server web服務器軟件名稱 Server: Apache/1.3.27 (Unix) (Red-Hat/Linux)
Set-Cookie 設置Http Cookie Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1
Trailer 指出頭域在分塊傳輸編碼的尾部存在 Trailer: Max-Forwards
Transfer-Encoding 文件傳輸編碼 Transfer-Encoding:chunked
Vary 告訴下游代理是使用緩存響應仍是從原始服務器請求 Vary: *
Via 告知代理客戶端響應是經過哪裏發送的 Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)
Warning 警告實體可能存在的問題 Warning: 199 Miscellaneous warning
WWW-Authenticate 代表客戶端請求實體應該使用的受權方案 WWW-Authenticate: Basic

2、 HTTP開發入門-靜態文件服務器

netty天生異步事件驅動的架構,不管是在性能上仍是在可靠性上,都表現優異,很是適合在非Web容器的場景下應用,相比於傳統的Tomcat,Jetty等Web容器,更加的輕量和小巧、靈活性和定製性也更好。

咱們以文件服務器爲例學習Netty的HTTP服務端入門開發,例程場景以下:

  •   文件服務器使用HTTP協議對外提供服務
  •   當客戶端經過瀏覽器訪問文件服務器時,對訪問路徑進行檢查,檢查失敗返回403
  •   檢查經過,以連接的方式打開當前文件目錄,每一個目錄或者都是個超連接,能夠遞歸訪問
  •   若是是目錄,能夠繼續遞歸訪問它下面的目錄或者文件,若是是文件而且可讀,則能夠在瀏覽器端直接打開,或者經過[目標另存爲]下載
 1 import io.netty.bootstrap.ServerBootstrap;
 2 import io.netty.channel.ChannelFuture;
 3 import io.netty.channel.ChannelInitializer;
 4 import io.netty.channel.EventLoopGroup;
 5 import io.netty.channel.nio.NioEventLoopGroup;
 6 import io.netty.channel.socket.SocketChannel;
 7 import io.netty.channel.socket.nio.NioServerSocketChannel;
 8 import io.netty.handler.codec.http.HttpObjectAggregator;
 9 import io.netty.handler.codec.http.HttpRequestDecoder;
10 import io.netty.handler.codec.http.HttpResponseEncoder;
11 import io.netty.handler.stream.ChunkedWriteHandler;
12 
13 /**
14  * @author lilinfeng
15  * @version 1.0
16  * @date 2014年2月14日
17  */
18 public class HttpFileServer {
19 
20     private static final String DEFAULT_URL = "/";
21 
22     public void run(final int port, final String url) throws Exception {
23         EventLoopGroup bossGroup = new NioEventLoopGroup();
24         EventLoopGroup workerGroup = new NioEventLoopGroup();
25         try {
26             ServerBootstrap b = new ServerBootstrap();
27             b.group(bossGroup, workerGroup)
28                     .channel(NioServerSocketChannel.class)
29                     .childHandler(new ChannelInitializer<SocketChannel>() {
30                         @Override
31                         protected void initChannel(SocketChannel ch)
32                                 throws Exception {
33                             ch.pipeline().addLast("http-decoder",
34                                     new HttpRequestDecoder()); // 請求消息解碼器
35                             ch.pipeline().addLast("http-aggregator",
36                                     new HttpObjectAggregator(65536));// 目的是將多個消息轉換爲單一的request或者response對象
37                             ch.pipeline().addLast("http-encoder",
38                                     new HttpResponseEncoder());//響應解碼器
39                             ch.pipeline().addLast("http-chunked",
40                                     new ChunkedWriteHandler());//目的是支持異步大文件傳輸()
41                             ch.pipeline().addLast("fileServerHandler",
42                                     new HttpFileServerHandler(url));// 業務邏輯
43                         }
44                     });
45             ChannelFuture future = b.bind("127.0.0.1", port).sync();
46             System.out.println("HTTP文件目錄服務器啓動,網址是 : " + "http://127.0.0.1:"
47                     + port + url);
48             future.channel().closeFuture().sync();
49         } catch (Exception e) {
50             e.printStackTrace();
51         } finally {
52             bossGroup.shutdownGracefully();
53             workerGroup.shutdownGracefully();
54         }
55     }
56 
57     public static void main(String[] args) throws Exception {
58         int port = 8080;
59         if (args.length > 0) {
60             try {
61                 port = Integer.parseInt(args[0]);
62             } catch (NumberFormatException e) {
63                 e.printStackTrace();
64             }
65         }
66         String url = DEFAULT_URL;
67         if (args.length > 1)
68             url = args[1];
69         new HttpFileServer().run(port, url);
70     }
71 }

 

重點在於編解碼器,首先添加的HTTP請求消息解碼器HttpRequestDecoder,而後是HttpObjectAggregator解碼器,它的做用是將多個消息轉換爲單一的FullHttpRequest或者FullHttpResponse,緣由是HTTP解碼器在每一個HTTP消息中會生成多個消息對象。

(1) HttpRequest/HttpResponse;

(2) HttpContent;

(3) LastHttpContent;

下面是FileServerHandler:

 

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.stream.ChunkedFile;
import io.netty.util.CharsetUtil;

import javax.activation.MimetypesFileTypeMap;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.regex.Pattern;

import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

/**
 * @author lilinfeng
 * @version 1.0
 * @date 2014年2月14日
 */
public class HttpFileServerHandler extends
        SimpleChannelInboundHandler<FullHttpRequest> {
    private final String url;

    public HttpFileServerHandler(String url) {
        this.url = url;
    }


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
        /*若是沒法解碼400*/
        if (!request.decoderResult().isSuccess()) {
            sendError(ctx, BAD_REQUEST);
            return;
        }

        /*只支持GET方法*/
        if (request.method() != GET) {
            sendError(ctx, METHOD_NOT_ALLOWED);
            return;
        }

        final String uri = request.uri();
        /*格式化URL,而且獲取路徑*/
        final String path = sanitizeUri(uri);
        if (path == null) {
            sendError(ctx, FORBIDDEN);
            return;
        }
        File file = new File(path);
        /*若是文件不可訪問或者文件不存在*/
        if (file.isHidden() || !file.exists()) {
            sendError(ctx, NOT_FOUND);
            return;
        }
        /*若是是目錄*/
        if (file.isDirectory()) {
            //1. 以/結尾就列出全部文件
            if (uri.endsWith("/")) {
                sendListing(ctx, file);
            } else {
                //2. 不然自動+/
                sendRedirect(ctx, uri + '/');
            }
            return;
        }
        if (!file.isFile()) {
            sendError(ctx, FORBIDDEN);
            return;
        }
        RandomAccessFile randomAccessFile = null;
        try {
            randomAccessFile = new RandomAccessFile(file, "r");// 以只讀的方式打開文件
        } catch (FileNotFoundException fnfe) {
            sendError(ctx, NOT_FOUND);
            return;
        }
        long fileLength = randomAccessFile.length();
        //建立一個默認的HTTP響應
        HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
        //設置Content Length
        HttpUtil.setContentLength(response, fileLength);
        //設置Content Type
        setContentTypeHeader(response, file);
        //若是request中有KEEP ALIVE信息
        if (HttpUtil.isKeepAlive(request)) {
            response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        }
        ctx.write(response);
        ChannelFuture sendFileFuture;
        //經過Netty的ChunkedFile對象直接將文件寫入發送到緩衝區中
        sendFileFuture = ctx.write(new ChunkedFile(randomAccessFile, 0,
                fileLength, 8192), ctx.newProgressivePromise());
        sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
            @Override
            public void operationProgressed(ChannelProgressiveFuture future,
                                            long progress, long total) {
                if (total < 0) { // total unknown
                    System.err.println("Transfer progress: " + progress);
                } else {
                    System.err.println("Transfer progress: " + progress + " / "
                            + total);
                }
            }

            @Override
            public void operationComplete(ChannelProgressiveFuture future)
                    throws Exception {
                System.out.println("Transfer complete.");
            }
        });
        ChannelFuture lastContentFuture = ctx
                .writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
        //若是不支持keep-Alive,服務器端主動關閉請求
        if (!HttpUtil.isKeepAlive(request)) {
            lastContentFuture.addListener(ChannelFutureListener.CLOSE);
        }
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        if (ctx.channel().isActive()) {
            sendError(ctx, INTERNAL_SERVER_ERROR);
        }
    }

    private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*");


    private String sanitizeUri(String uri) {
        try {
            uri = URLDecoder.decode(uri, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            try {
                uri = URLDecoder.decode(uri, "ISO-8859-1");
            } catch (UnsupportedEncodingException e1) {
                throw new Error();
            }
        }
        if (!uri.startsWith(url)) {
            return null;
        }
        if (!uri.startsWith("/")) {
            return null;
        }
        uri = uri.replace('/', File.separatorChar);
        if (uri.contains(File.separator + '.')
                || uri.contains('.' + File.separator) || uri.startsWith(".")
                || uri.endsWith(".") || INSECURE_URI.matcher(uri).matches()) {
            return null;
        }
        return System.getProperty("user.dir") + File.separator + uri;
    }

    private static final Pattern ALLOWED_FILE_NAME = Pattern
            .compile("[A-Za-z0-9][-_A-Za-z0-9\\.]*");

    private static void sendListing(ChannelHandlerContext ctx, File dir) {
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK);
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
        StringBuilder buf = new StringBuilder();
        String dirPath = dir.getPath();
        buf.append("<!DOCTYPE html>\r\n");
        buf.append("<html><head><title>");
        buf.append(dirPath);
        buf.append(" 目錄:");
        buf.append("</title></head><body>\r\n");
        buf.append("<h3>");
        buf.append(dirPath).append(" 目錄:");
        buf.append("</h3>\r\n");
        buf.append("<ul>");
        buf.append("<li>連接:<a href=\"../\">..</a></li>\r\n");
        for (File f : dir.listFiles()) {
            if (f.isHidden() || !f.canRead()) {
                continue;
            }
            String name = f.getName();
            if (!ALLOWED_FILE_NAME.matcher(name).matches()) {
                continue;
            }
            buf.append("<li>連接:<a href=\"");
            buf.append(name);
            buf.append("\">");
            buf.append(name);
            buf.append("</a></li>\r\n");
        }
        buf.append("</ul></body></html>\r\n");
        ByteBuf buffer = Unpooled.copiedBuffer(buf, CharsetUtil.UTF_8);
        response.content().writeBytes(buffer);
        buffer.release();
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    private static void sendRedirect(ChannelHandlerContext ctx, String newUri) {
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, FOUND);
        response.headers().set(HttpHeaderNames.LOCATION, newUri);
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    private static void sendError(ChannelHandlerContext ctx,
                                  HttpResponseStatus status) {
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1,
                status, Unpooled.copiedBuffer("Failure: " + status.toString()
                + "\r\n", CharsetUtil.UTF_8));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    private static void setContentTypeHeader(HttpResponse response, File file) {
        MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();
        response.headers().set(HttpHeaderNames.CONTENT_TYPE,
                mimeTypesMap.getContentType(file.getPath()));
    }
}

 

上面的代碼註釋相對詳細,這裏大體梳理一下。

(1) 不能解碼返回400,只支持GET請求,不然返回405.

(2) 對url包裝,使用UTF-8字符集,轉換爲絕對path

(3) 若是是目錄,建立一個html頁面

(4) 若是是文件,設置content-type和content-length,使用netty的chunkedfile直接寫到緩衝,異步的方式

(5) 若是是非keepalived的,服務器端主動關閉,不然等待客戶端主動關閉。

3、Netty HTTP+Json協議棧開發

說明:原書是使用XML協議開發,XML框架用的是JiBX,這裏用的是Json。

3.1 業務場景與協議設計

咱們模擬一個簡單的用戶訂購系統。

訂購請求信息:

字段名稱 類型 備註
訂購數量 Int64 訂購的商品數量
客戶信息 Customer 客戶信息,負責POJO對象
帳單地址 Address 帳單的地址
寄送方式 Shipping

枚舉類型以下:

  普通郵寄

  宅急送

  國際郵遞

  國內快遞

  國際快遞

送貨地址 Address 送貨地址
總價 float 商品總價

客戶信息定義:

字段名稱 類型 備註
客戶ID Int64 客戶ID,長整型
String 客戶姓氏,字符串
String 客戶名字,字符串
全名 List<String> 客戶全稱,字符列表

地址信息:

字段名稱 類型 備註
街道1 String  
街道2 String  
城市 String  
省份 String  
郵編 String  
國家 String  

郵遞方式:

字段名稱 類型 備註
普通郵遞 枚舉類型  
宅急送 枚舉類型  
國際郵遞 枚舉類型  
國內快遞 枚舉類型  
國際快遞 枚舉類型  

流程設計以下:

  1. client端構造訂閱請求消息,將請求消息編碼爲HTTP+json格式
  2. client端發起鏈接,經過HTTP協議棧發送HTTP請求消息
  3. server端對HTTP+json請求消息進行解碼,解碼成請求POJO
  4. server端構造應答消息並編碼,經過HTTP+json方式返回給客戶端
  5. client端對HTTP+json響應消息進行解碼,解碼成響應POJO

3.2 服務端編解碼器

涉及的類比較多:

 

netty開發的關鍵在於各類編解碼器。

首先定義本身的請求類和響應類

 

import io.netty.handler.codec.http.FullHttpRequest;

/**
 * @author Lilinfeng
 * @version 1.0
 * @date 2014年3月1日
 */
public class HttpJsonRequest {

    private FullHttpRequest request;
    private Object body;

    public HttpJsonRequest(FullHttpRequest request, Object body) {
        this.request = request;
        this.body = body;
    }

    /**
     * @return the request
     */
    public final FullHttpRequest getRequest() {
        return request;
    }

    /**
     * @param request the request to set
     */
    public final void setRequest(FullHttpRequest request) {
        this.request = request;
    }

    /**
     * @return the object
     */
    public final Object getBody() {
        return body;
    }

    /**
     * @param object the object to set
     */
    public final void setBody(Object body) {
        this.body = body;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "HttpJsonRequest [request=" + request + ", body =" + body + "]";
    }
}
import io.netty.handler.codec.http.FullHttpResponse;

/**
 * @author Administrator
 * @version 1.0
 * @date 2014年3月1日
 */
public class HttpJsonResponse {
    private FullHttpResponse httpResponse;
    private Object result;

    public HttpJsonResponse(FullHttpResponse httpResponse, Object result) {
        this.httpResponse = httpResponse;
        this.result = result;
    }

    /**
     * @return the httpResponse
     */
    public final FullHttpResponse getHttpResponse() {
        return httpResponse;
    }

    /**
     * @param httpResponse the httpResponse to set
     */
    public final void setHttpResponse(FullHttpResponse httpResponse) {
        this.httpResponse = httpResponse;
    }

    /**
     * @return the body
     */
    public final Object getResult() {
        return result;
    }

    /**
     * @param body the body to set
     */
    public final void setResult(Object result) {
        this.result = result;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "HttpJsonResponse [httpResponse=" + httpResponse + ", result="
                + result + "]";
    }

}

根據這2個類來設計流程,咱們可使用netty對http協議支持的編解碼器,首先咱們使用了FastJson來做爲json的框架,所以先定義2個抽象類,其中封裝了json的轉換方法,雖然看上去有點複雜,可是僅僅封裝了json化和反json化方法。

import demo.protocol.http.json.FastJsonUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;

import java.nio.charset.Charset;

/**
 * Created by carl.yu on 2016/12/16.
 */
public abstract class AbstractHttpJsonEncoder<T> extends MessageToMessageEncoder<T> {
    final static Charset UTF_8 = Charset.forName("utf-8");

    protected ByteBuf encode0(ChannelHandlerContext ctx, Object body) {
        String jsonStr = FastJsonUtils.convertObjectToJSON(body);
        ByteBuf encodeBuf = Unpooled.copiedBuffer(jsonStr, UTF_8);
        return encodeBuf;
    }

}
import demo.protocol.http.json.FastJsonUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;

import java.nio.charset.Charset;

/**
 * Created by carl.yu on 2016/12/16.
 */
public abstract class AbstractHttpJsonDecoder<T> extends MessageToMessageDecoder<T> {
    private Class<?> clazz;
    private boolean isPrint;
    private final static Charset UTF_8 = Charset.forName("UTF-8");

    protected AbstractHttpJsonDecoder(Class<?> clazz) {
        this(clazz, false);
    }

    protected AbstractHttpJsonDecoder(Class<?> clazz, boolean isPrint) {
        this.clazz = clazz;
        this.isPrint = isPrint;
    }

    protected Object decode0(ChannelHandlerContext ctx, ByteBuf body) {
        String content = body.toString(UTF_8);
        if (isPrint)
            System.out.println("The body is : " + content);
        Object result = FastJsonUtils.convertJSONToObject(content, clazz);
        return result;
    }


}

 

服務端編解碼器: 獲取請求,最終解碼爲自定義的HttpJsonRequest對象

(1) HttpRequestDecoder:請求消息解碼器,轉換爲消息對象。

(2) HttpObjectAggregator: 目的是將多個消息轉換爲單一的request或者response對象,最終獲得的是FullHttpRequest對象

(3) 須要自定義的解碼器HttpJsonRequestDecoder,將FullHttpRequest轉換爲HttpJsonRequest對象

服務器端編碼器:發送響應,將生成的數據轉換爲DefaultFullHttpResponse對象發送出去.

(1) HttpResponseEncoder:響應消息編碼器,已是一個HTTP消息了

(2) 自定義編碼器 HttpJsonResponseEncoder:因爲Netty的DefaultFullHttpResponse沒有提供動態設置消息體content的接口。所以咱們只能複製一個新的HTTP消息,將動態內容加入,生成一個DefaultFullHttpResponse對象。

上面涉及到的類以下:

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.CharsetUtil;

import java.util.List;

import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

/**
 * Created by carl.yu on 2016/12/16.
 */
public class HttpJsonRequestDecoder extends AbstractHttpJsonDecoder<FullHttpRequest> {

    public HttpJsonRequestDecoder(Class<?> clazz) {
        this(clazz, false);
    }

    /**
     * 構造器
     *
     * @param clazz   解碼的對象信息
     * @param isPrint 是否須要打印
     */
    public HttpJsonRequestDecoder(Class<?> clazz, boolean isPrint) {
        super(clazz, isPrint);
    }

    /**
     * @param ctx channel上下文
     * @param msg 消息
     * @param out 輸出集合
     * @throws Exception
     */
    @Override
    protected void decode(ChannelHandlerContext ctx, FullHttpRequest msg, List<Object> out) throws Exception {
        if (!msg.decoderResult().isSuccess()) {
            sendError(ctx, HttpResponseStatus.BAD_REQUEST);
            return;
        }
        HttpJsonRequest request = new HttpJsonRequest(msg, decode0(ctx, msg.content()));
        out.add(request);
    }


    /**
     * 測試的話,直接封裝,實戰中須要更健壯的處理
     */
    private static void sendError(ChannelHandlerContext ctx,
                                  HttpResponseStatus status) {
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1,
                status, Unpooled.copiedBuffer("Failure: " + status.toString()
                + "\r\n", CharsetUtil.UTF_8));
        response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }
}
/**
 * Created by carl.yu on 2016/12/16.
 */
public class HttpJsonResponseEncoder extends AbstractHttpJsonEncoder<HttpJsonResponse> {
    @Override
    protected void encode(ChannelHandlerContext ctx, HttpJsonResponse msg, List<Object> out) throws Exception {
        //編碼
        ByteBuf body = encode0(ctx, msg.getResult());
        FullHttpResponse response = msg.getHttpResponse();
        if (response == null) {
            response = new DefaultFullHttpResponse(HTTP_1_1, OK, body);
        } else {
            response = new DefaultFullHttpResponse(msg.getHttpResponse()
                    .protocolVersion(), msg.getHttpResponse().status(),
                    body);
        }
        response.headers().set(CONTENT_TYPE, "text/json");
        HttpUtil.setContentLength(response, body.readableBytes());
        out.add(response);
    }


}

3.3 客戶端編解碼器

客戶端解碼器:

  • 使用netty自帶的HttpResponseDecoder和HttpObjectAggregator將響應解碼成FullHttpResponse
  • 使用自定義的解碼器HttpJsonResponseDecoder封裝爲使用的HttpJsonResponse

客戶端編碼器

  • 使用netty自帶的HttpRequestEncoder寫入HttpJsonRequest
  • 使用自定義的編碼器HttpJsonRequestEncoder封裝成FullHttpRequest而後發送
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;

import java.net.InetAddress;
import java.util.List;

/**
 * Created by carl.yu on 2016/12/16.
 */
public class HttpJsonRequestEncoder extends AbstractHttpJsonEncoder<HttpJsonRequest> {
    @Override
    protected void encode(ChannelHandlerContext ctx, HttpJsonRequest msg, List<Object> out) throws Exception {
        //(1)調用父類的encode0,將業務須要發送的對象轉換爲Json
        ByteBuf body = encode0(ctx, msg.getBody());
        //(2) 若是業務自定義了HTTP消息頭,則使用業務的消息頭,不然在這裏構造HTTP消息頭
        // 這裏使用硬編碼的方式來寫消息頭,實際中能夠寫入配置文件
        FullHttpRequest request = msg.getRequest();
        if (request == null) {
            request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
                    HttpMethod.GET, "/do", body);
            HttpHeaders headers = request.headers();
            headers.set(HttpHeaderNames.HOST, InetAddress.getLocalHost()
                    .getHostAddress());
            headers.set(HttpHeaderNames.CONNECTION, HttpHeaders.Values.CLOSE);
            headers.set(HttpHeaderNames.ACCEPT_ENCODING,
                    HttpHeaderValues.GZIP.toString() + ','
                            + HttpHeaderValues.DEFLATE.toString());
            headers.set(HttpHeaderNames.ACCEPT_CHARSET,
                    "ISO-8859-1,utf-8;q=0.7,*;q=0.7");
            headers.set(HttpHeaderNames.ACCEPT_LANGUAGE, "zh");
            headers.set(HttpHeaderNames.USER_AGENT,
                    "Netty json Http Client side");
            headers.set(HttpHeaderNames.ACCEPT,
                    "text/html,application/json;q=0.9,*/*;q=0.8");
        }
        HttpUtil.setContentLength(request, body.readableBytes());
        // (3) 編碼後的對象
        out.add(request);
    }


}
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpResponse;

import java.util.List;


/**
 * Created by carl.yu on 2016/12/16.
 */
public class HttpJsonResponseDecoder extends AbstractHttpJsonDecoder<FullHttpResponse> {

    public HttpJsonResponseDecoder(Class<?> clazz) {
        this(clazz, false);
    }

    /**
     * 構造器
     *
     * @param clazz   解碼的對象信息
     * @param isPrint 是否須要打印
     */
    public HttpJsonResponseDecoder(Class<?> clazz, boolean isPrint) {
        super(clazz, isPrint);
    }

    /**
     * @param ctx channel上下文
     * @param msg 消息
     * @param out 輸出集合
     * @throws Exception
     */
    @Override
    protected void decode(ChannelHandlerContext ctx, FullHttpResponse msg, List<Object> out) throws Exception {
        System.out.println("開始解碼...");
        out.add(
                new HttpJsonResponse(msg, decode0(ctx, msg.content()))
        );
    }


}

3.4 開發HttpServer和HttpClient

server部分:

import demo.protocol.http.json.codec.HttpJsonRequestDecoder;
import demo.protocol.http.json.codec.HttpJsonResponseEncoder;
import demo.protocol.http.json.pojo.Order;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;

import java.net.InetSocketAddress;

/**
 * Created by carl.yu on 2016/12/16.
 */
public class HttpJsonServer {
    public void run(final int port) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch)
                                throws Exception {
                            //接收HttpJsonRequest,須要對應解碼器
                            //ByteBuf->FullHttpRequest-> HttpJsonRequestDecoder
                            //輸出HttpJsonResponse,須要對應編碼器
                            //HttpResponseEncoder->FullHttpResponse-> HttpJsonResponseEncoder
                            ch.pipeline().addLast("http-decoder", new HttpRequestDecoder());
                            ch.pipeline().addLast("http-aggregator", new HttpObjectAggregator(65536));
                            ch.pipeline().addLast("json-decoder", new HttpJsonRequestDecoder(Order.class, true));
                            ch.pipeline().addLast("http-encoder", new HttpResponseEncoder());
                            ch.pipeline().addLast("json-encoder", new HttpJsonResponseEncoder());
                            ch.pipeline().addLast("jsonServerHandler", new HttpJsonServerHandler());
                        }
                    });
            ChannelFuture future = b.bind(new InetSocketAddress(port)).sync();
            System.out.println("HTTP訂購服務器啓動,網址是 : " + "http://localhost:"
                    + port);
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        if (args.length > 0) {
            try {
                port = Integer.parseInt(args[0]);
            } catch (NumberFormatException e) {
                e.printStackTrace();
            }
        }
        new HttpJsonServer().run(port);
    }
}
import demo.protocol.http.json.codec.HttpJsonRequest;
import demo.protocol.http.json.codec.HttpJsonResponse;
import demo.protocol.http.json.pojo.Address;
import demo.protocol.http.json.pojo.Order;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;

import java.util.ArrayList;
import java.util.List;

import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

/**
 * Created by carl.yu on 2016/12/16.
 */
public class HttpJsonServerHandler extends SimpleChannelInboundHandler<HttpJsonRequest> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpJsonRequest msg) throws Exception {
        HttpRequest request = msg.getRequest();
        Order order = (Order) msg.getBody();
        System.out.println("Http server receive request : " + order);
        dobusiness(order);
        ChannelFuture future = ctx.writeAndFlush(new HttpJsonResponse(null, order));
        if (!HttpUtil.isKeepAlive(request)) {
            future.addListener(new GenericFutureListener<Future<? super Void>>() {
                public void operationComplete(Future future) throws Exception {
                    ctx.close();
                }
            });
        }
    }

    private void dobusiness(Order order) {
        order.getCustomer().setFirstName("狄");
        order.getCustomer().setLastName("仁杰");
        List<String> midNames = new ArrayList<String>();
        midNames.add("李元芳");
        order.getCustomer().setMiddleNames(midNames);
        Address address = order.getBillTo();
        address.setCity("洛陽");
        address.setCountry("大唐");
        address.setState("河南道");
        address.setPostCode("123456");
        order.setBillTo(address);
        order.setShipTo(address);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        if (ctx.channel().isActive()) {
            sendError(ctx, INTERNAL_SERVER_ERROR);
        }
    }

    private static void sendError(ChannelHandlerContext ctx,
                                  HttpResponseStatus status) {
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1,
                status, Unpooled.copiedBuffer("失敗: " + status.toString()
                + "\r\n", CharsetUtil.UTF_8));
        response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }
}

Client部分:

import demo.protocol.http.json.codec.HttpJsonRequestEncoder;
import demo.protocol.http.json.codec.HttpJsonResponseDecoder;
import demo.protocol.http.json.pojo.Order;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestEncoder;
import io.netty.handler.codec.http.HttpResponseDecoder;

import java.net.InetSocketAddress;

/**
 * Created by carl.yu on 2016/12/16.
 */
public class HttpJsonClient {
    public void connect(int port) throws Exception {
        // 配置客戶端NIO線程組
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch)
                                throws Exception {
                            ch.pipeline().addLast("http-decoder",
                                    new HttpResponseDecoder());
                            ch.pipeline().addLast("http-aggregator",
                                    new HttpObjectAggregator(65536));
                            // json解碼器
                            ch.pipeline().addLast("json-decoder", new HttpJsonResponseDecoder(Order.class, true));
                            ch.pipeline().addLast("http-encoder",
                                    new HttpRequestEncoder());
                            ch.pipeline().addLast("json-encoder",
                                    new HttpJsonRequestEncoder());
                            ch.pipeline().addLast("jsonClientHandler",
                                    new HttpJsonClientHandler());
                        }
                    });

            // 發起異步鏈接操做
            ChannelFuture f = b.connect(new InetSocketAddress(port)).sync();

            // 當代客戶端鏈路關閉
            f.channel().closeFuture().sync();
        } finally {
            // 優雅退出,釋放NIO線程組
            group.shutdownGracefully();
        }
    }

    /**
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
                // 採用默認值
            }
        }
        new HttpJsonClient().connect(port);
    }
}
import demo.protocol.http.json.codec.HttpJsonRequest;
import demo.protocol.http.json.pojo.OrderFactory;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * Created by carl.yu on 2016/12/16.
 */
public class HttpJsonClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("鏈接上服務器...");
        HttpJsonRequest request = new HttpJsonRequest(null, OrderFactory.create(123));
        ctx.writeAndFlush(request);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println(msg.getClass().getName());
        System.out.println("接收到了數據..." + msg);
    }

    /*@Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpJsonResponse msg) throws Exception {
        System.out.println("The client receive response of http header is : "
                + msg.getHttpResponse().headers().names());
        System.out.println("The client receive response of http body is : "
                + msg.getResult());
    }*/


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

運行便可。

相關文章
相關標籤/搜索