Netty authoritative guide Http protocol development

3.1 Http protocol development

3.1.1 basic knowledge

  • HTTP (Hypertext Transfer Protocol) protocol is an application layer protocol based on TCP transmission protocol. Its development is the result of the cooperation between the World Wide Web association and the Internet working group IETF. HTTP is an object-oriented protocol belonging to the application layer. Because of its simple and fast way, it is suitable for distributed hypermedia information system. It was put forward in 1990. After years of use and development, it has been continuously improved and expanded.
  • As HTTP protocol is the mainstream protocol of Web development at present, the application based on HTTP is very extensive. Therefore, it is very important to master the development of HTTP.
  • HTTP is an object-oriented protocol belonging to the application layer. Because of its simple and fast way, it is suitable for distributed hypermedia information system.
  • The main features of HTTP protocol are as follows. Support Client/Server mode.
  • Simple - when a client requests a service from the server, it only needs to specify the service URL and carry the necessary request parameters or the message body in the.
  • Flexible - HTTP allows the transmission of any type of data object, and the transmitted content type is marked by the content type in the HTTP message header.
  • Stateless -- HTTP protocol is a stateless protocol. Stateless means that the protocol has no memory ability for transaction processing. The lack of status means that if the previous information is required for subsequent processing, it must be retransmitted, which may increase the amount of data transmitted per connection. On the other hand, when the server does not need previous information, its response is faster and the load is lighter.

3.1.2 HTTP request message (HttpRequest)

  • The request message that the client sends an HTTP request to the server includes the following formats: request line, request header, blank line and request data. The following figure shows the general format of the request message.

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

Request method
A variety of HTTP request methods can be used according to the HTTP request standard.
HTTP1.0 defines three request methods: GET, POST and HEAD.
HTTP1.1. Five request methods are added: OPTIONS, PUT, DELETE, TRACE and CONNECT methods.

Serial numbermethoddescribe
1GETRequest the specified page information and return the entity body.
2HEADIt is similar to the get request, but there is no specific content in the returned response, which is used to get the header
3POSTSubmit data to specified resources for processing requests (such as submitting forms or uploading files). The data is contained in the request body. POST requests may lead to the establishment of new resources and / or the modification of existing resources.
4PUTThe data transmitted from the client to the server replaces the content of the specified document.
5DELETERequests the server to delete the specified page.
6CONNECTThe HTTP/1.1 protocol is reserved for proxy servers that can change the connection to pipeline.
7OPTIONSAllows clients to view the performance of the server.
8TRACEEcho the request received by the server, which is mainly used for testing or diagnosis.

Message header

3.1.3 HTTP response message (HttpResponse)

  • The HTTP response also consists of four parts: status line, message header, blank line and response body.


Status code
When visitors visit a web page, their browser will send a request to the server where the web page is located. Before the browser receives and displays the web page, the server where the web page is located will return a server header containing HTTP status code to respond to the browser's request.
The English of HTTP Status Code is HTTP Status Code.
The following are common HTTP status codes:

  • 200 - Request successful
  • 301 - resources (web pages, etc.) are permanently transferred to other URL s
  • 404 - the requested resource (web page, etc.) does not exist
  • 500 - internal server error


Response header message

HeaderexplainExamples
Accept-RangesIndicates whether the server supports the specified range request and what type of segmentation requestAccept-Ranges: bytes
AgeEstimated time from original server to proxy cache formation (in seconds, non negative)Age: 12
AllowIf a valid request behavior for a network resource is not allowed, 405 is returnedAllow: GET, HEAD
Cache-ControlTell all caching mechanisms whether they can cache and what typeCache-Control: no-cache
Content-EncodingThe compression encoding type of returned content supported by the web server.Content-Encoding: gzip
Content-LanguageResponse languageContent-Language: en,zh
Content-LengthLength of response bodyContent-Length: 348
Content-LocationAnother alternate address where the requested resource can be replacedContent-Location: /index.htm
Content-MD5Returns the MD5 check value of the resourceContent-MD5: Q2hlY2sgSW50ZWdyaXR5IQ==
Content-RangeThe byte position of this part in the whole return bodyContent-Range: bytes 21010-47021/47022
Content-TypeReturns the MIME type of the contentContent-Type: text/html; charset=utf-8
DateThe time when the original server message was sentDate: Tue, 15 Nov 2010 08:12:31 GMT
ETagThe current value of the entity label of the request variableETag: "737060cd8c284d8af7ad3082f209582d"
ExpiresDate and time the response expiresExpires: Thu, 01 Dec 2010 16:00:00 GMT
Last-ModifiedLast time the resource was modifiedLast-Modified: Tue, 15 Nov 2010 12:45:26 GMT
LocationUsed to redirect the receiver to the location of the non request URL to complete the request or identify a new resourceLocation: http://www.zcmhi.com/archives/94.html
PragmaThis includes implementing specific instructions that can be applied to any receiver in the response chainPragma: no-cache
Proxy-AuthenticateIt indicates the authentication scheme and the parameters on the URL that can be applied to the agentProxy-Authenticate: Basic
refreshApply to redirection or a new resource is created and redirected after 5 seconds (proposed by Netscape and supported by most browsers)

Refresh: 5; url=
http://www.zcmhi.com/archives/94.html |
|Retry after | if the entity is temporarily unavailable, notify the client to try again after the specified time | retry after: 120|
|Server | web server software name | server: Apache / 1.3.27 (Unix) (red hat / Linux)|
|Set cookie | set HTTP cookie | set Cookie: userid = johndoe; Max-Age=3600; Version=1 |
|Trailer | indicates that the header field exists at the end of the block transmission code | Trailer: Max forwards|
|Transfer encoding | file transfer encoding | transfer encoding: chunked|
|Vary | tell the downstream agent whether to use cached response or request from the original server | vary: *|
|Via | tell the proxy client where the response is sent | via: 1.0 Fred, 1.1 nowhere com (Apache/1.1) |
|Warning | warning of possible problems in the entity | Warning: 199 Miscellaneous warning|
|Www authenticate | indicates the authorization scheme that the client requesting entity should use | www authenticate: Basic|

3.1.4 Http development

netty's naturally asynchronous event driven architecture performs well both in performance and reliability. It is very suitable for applications in non Web container scenarios. Compared with traditional Web containers such as Tomcat and Jetty, it is more lightweight, compact, flexible and customizable.
Let's take the file server as an example to learn the introduction development of HTTP server of Netty. The routine scenario is as follows:

  • The file server uses HTTP protocol to provide external services
  • When the client accesses the file server through the browser, the access path is checked. If the check fails, 403 is returned
  • After checking, open the current file directory in the form of link. Each directory or is a hyperlink, which can be accessed recursively
  • If it is a directory, you can continue to recursively access the directory or file below it. If it is a file and readable, you can open it directly in the browser or download it through [save target as]
package com.shu.HttpProtocol;

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 io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;

/**
 * @Author shu
 * @Version 1.0
 * @Date: 2022/03/07/ 9:53
 * @Description Http File request
 **/

public class HttpFileServer {

    private static final String DEFAULT_URL = "/";

    public void run(final int port, final String url) 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 {
                            ch.pipeline().addLast("log",
                                    new LoggingHandler(LogLevel.DEBUG));//Log processing
                            ch.pipeline().addLast("http-decoder",
                                    new HttpRequestDecoder()); // Request message decoder
                            ch.pipeline().addLast("http-aggregator",
                                    new HttpObjectAggregator(65536));// The purpose is to convert multiple messages into a single request or response object
                            ch.pipeline().addLast("http-encoder",
                                    new HttpResponseEncoder());//Response decoder
                            ch.pipeline().addLast("http-chunked",
                                    new ChunkedWriteHandler());//The purpose is to support asynchronous large file transfer ()
                            ch.pipeline().addLast("fileServerHandler",
                                    new HttpFileServerHandler(url));// Business logic
                        }
                    });
            ChannelFuture future = b.bind("127.0.0.1", port).sync();
            System.out.println("HTTP The file directory server starts. The website is : " + "http://127.0.0.1:"
                    + port + url);
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } 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();
            }
        }
        String url = DEFAULT_URL;
        if (args.length > 1)
            url = args[1];
        new HttpFileServer().run(port, url);
    }
}

package com.shu.HttpProtocol;

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 shu
 * @Version 1.0
 * @Date: 2022/03/07/ 9:55
 * @Description File request custom processor
 **/

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 {
        /*If you can't decode 400*/
        if (!request.decoderResult().isSuccess()) {
            sendError(ctx, BAD_REQUEST);
            return;
        }

        /*Only GET methods are supported*/
        if (request.method() != GET) {
            sendError(ctx, METHOD_NOT_ALLOWED);
            return;
        }

        final String uri = request.uri();
        /*Format the URL and get the path*/
        final String path = sanitizeUri(uri);
        if (path == null) {
            sendError(ctx, FORBIDDEN);
            return;
        }
        File file = new File(path);
        /*If the file is inaccessible or does not exist*/
        if (file.isHidden() || !file.exists()) {
            sendError(ctx, NOT_FOUND);
            return;
        }
        /*If it is a directory*/
        if (file.isDirectory()) {
            //1. List all documents at the end of /
            if (uri.endsWith("/")) {
                sendListing(ctx, file);
            } else {
                //2. Otherwise, automatic+/
                sendRedirect(ctx, uri + '/');
            }
            return;
        }
        if (!file.isFile()) {
            sendError(ctx, FORBIDDEN);
            return;
        }
        RandomAccessFile randomAccessFile = null;
        try {
            randomAccessFile = new RandomAccessFile(file, "r");// Open the file as read-only
        } catch (FileNotFoundException fnfe) {
            sendError(ctx, NOT_FOUND);
            return;
        }
        long fileLength = randomAccessFile.length();
        //Create a default HTTP response
        HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
        //Set Content Length
        HttpUtil.setContentLength(response, fileLength);
        //Set Content Type
        setContentTypeHeader(response, file);
        //If there is KEEP ALIVE information in the request
        if (HttpUtil.isKeepAlive(request)) {
            response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        }
        ctx.write(response);
        ChannelFuture sendFileFuture;
        //The file is written and sent directly to the buffer through the ChunkedFile object of Netty
        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);
        //If keep alive is not supported, the server actively closes the request
        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(" catalog:");
        buf.append("</title></head><body>\r\n");
        buf.append("<h3>");
        buf.append(dirPath).append(" catalog:");
        buf.append("</h3>\r\n");
        buf.append("<ul>");
        buf.append("<li>Link:<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>Link:<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()));
    }
}

  • HttpServerCodec: as a server, HttpServerCodec is used as an encoder and decoder
  • HttpObjectAggregator: it is used to convert multiple messages into a single FullHttpRequest or FullHttpResponse because the HTTP decoder will generate multiple message objects in each HTTP message.
  • ChunkedWriteHandler: its main function is to support asynchronous sending of large code streams (such as large file transfers), but it does not occupy too much memory to prevent Java memory overflow errors.

3.1.5 development of netty HTTP + JSON protocol stack


  • Entity class
package com.shu.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @Author shu
 * @Version 1.0
 * @Date: 2022/03/07/ 20:41
 * @Description address
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Address {
    private String street; // street
    private String city; // city
    private String province; //province
    private String postcode; //Zip code
    private String state; // country
}


package com.shu.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @Author shu
 * @Version 1.0
 * @Date: 2022/03/07/ 20:58
 * @Description customer
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Customer {
    private int id;
    private String userName;
    private String phone;
}


package com.shu.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @Author shu
 * @Version 1.0
 * @Date: 2022/03/07/ 20:56
 * @Description order
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
    private int count;
    private Customer customer;
    private Address address;
    private Shipping shipping;
    private Float countPrice;
}

package com.shu.pojo;

/**
 * @Author shu
 * @Version 1.0
 * @Date: 2022/03/07/ 20:52
 * @Description Sending method
 **/
public enum Shipping {

    A(0, "Ordinary mail"),
    B(100, "House Express"),
    C(-100, "International Express Service"),
    D(200, "Domestic express");

    private int code;
    private String desc;

    Shipping(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }
}

  • Abstract Http Json encoder
package com.shu.codec;

import com.alibaba.fastjson.JSON;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/**
 * @Author shu
 * @Version 1.0
 * @Date: 2022/03/07/ 21:04
 * @Description Abstract Http Json encoder
 **/
@Slf4j
public abstract class AbstractHttpJsonEncoder<T>  extends MessageToMessageEncoder<T> {

    final static Charset UTF_8 = StandardCharsets.UTF_8;
    //code
    protected ByteBuf encode0(ChannelHandlerContext ctx, Object body) {
        log.info("Start coding: convert the message into ByteBuf");
        //Convert message to JSON
        String jsonStr = JSON.toJSONString(body);
        // Convert to ByteBuf
        return  Unpooled.copiedBuffer(jsonStr, UTF_8);
    }
}
  • Abstract Http Json decoder
package com.shu.codec;

import com.alibaba.fastjson.JSON;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/**
 * @Author shu
 * @Version 1.0
 * @Date: 2022/03/07/ 21:15
 * @Description Abstract Http Json decoder
 **/
public abstract class AbstractHttpJsonDecoder<T> extends MessageToMessageDecoder<T> {

    private final Class<?> clazz;
    private final boolean isPrint;
    private final static Charset UTF_8 = StandardCharsets.UTF_8;


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

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

    // decode
    protected Object decode0(ChannelHandlerContext ctx, ByteBuf body) {
        String content = body.toString(UTF_8);

        if (isPrint)
            System.out.println("The body is : " + content);

        Object result = JSON.parseObject(content, clazz);
        return result;
    }


}
  • Request entity class
package com.shu.codec;

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

/**
 * @Author shu
 * @Version 1.0
 * @Date: 2022/03/07/ 21:02
 * @Description Http Json request
 **/
public class HttpJsonRequest {
    // Complete Http request
    private FullHttpRequest request;
    // Message body
    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 + "]";
    }
}

  • Response entity class
package com.shu.codec;

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

/**
 * @Author shu
 * @Version 1.0
 * @Date: 2022/03/07/ 21:03
 * @Description Http Json response
 **/
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 + "]";
    }
}
  • Request encoding and decoding
package com.shu.codec;

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.HttpVersion.HTTP_1_1;
import static io.netty.handler.codec.stomp.StompHeaders.CONTENT_TYPE;

/**
 * @Author shu
 * @Version 1.0
 * @Date: 2022/03/07/ 21:17
 * @Description Http Json Request decoder
 **/
public class HttpJsonRequestDecoder extends AbstractHttpJsonDecoder<FullHttpRequest> {

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


    /**
     * constructor 
     *
     * @param clazz   Decoded object information
     * @param isPrint Need to print
     */
    public HttpJsonRequestDecoder(Class<?> clazz, boolean isPrint) {
        super(clazz, isPrint);
    }

    /**
     * @param ctx channel context
     * @param msg news
     * @param out Output set
     * @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);
    }


    /**
     * If the test is carried out, it should be directly encapsulated. In practice, it needs more robust processing
     */
    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);
    }
}
package com.shu.codec;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;

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

/**
 * @Author shu
 * @Version 1.0
 * @Date: 2022/03/07/ 21:19
 * @Description Http Json Request encoder
 **/
public class HttpJsonRequestEncoder extends AbstractHttpJsonEncoder<HttpJsonRequest> {
    /**
     * Encapsulate the request message into Http message - > request line, request header, empty line and request data
     * @param ctx
     * @param msg
     * @param out
     * @throws Exception
     */
    @Override
    protected void encode(ChannelHandlerContext ctx, HttpJsonRequest msg, List<Object> out) throws Exception {
        //(1) Call the encode0 of the parent class to convert the objects to be sent by the business into Json
        ByteBuf body = encode0(ctx, msg.getBody());
        //(2) If the business customizes the HTTP header, the business header is used. Otherwise, the HTTP header is constructed here
        // Here, the message header is written by hard coding, and the configuration file can be written in practice
        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) Encoded object
        out.add(request);
    }
}
  • Response codec
package com.shu.codec;

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

import java.util.List;

/**
 * @Author shu
 * @Version 1.0
 * @Date: 2022/03/07/ 21:19
 * @Description Http Json Response decoder
 **/
public class HttpJsonResponseDecoder extends AbstractHttpJsonDecoder<FullHttpResponse> {

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

    /**
     * constructor 
     *
     * @param clazz   Decoded object information
     * @param isPrint Need to print
     */
    public HttpJsonResponseDecoder(Class<?> clazz, boolean isPrint) {
        super(clazz, isPrint);
    }

    /**
     * @param ctx channel context
     * @param msg news
     * @param out Output set
     * @throws Exception
     */
    @Override
    protected void decode(ChannelHandlerContext ctx, FullHttpResponse msg, List<Object> out) throws Exception {
        System.out.println("Start decoding...");
        out.add(
                new HttpJsonResponse(msg, decode0(ctx, msg.content()))
        );
    }


}
package com.shu.codec;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpUtil;

import java.util.List;

import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import static io.netty.handler.codec.stomp.StompHeaders.CONTENT_TYPE;

/**
 * @Author shu
 * @Version 1.0
 * @Date: 2022/03/07/ 21:18
 * @Description Http Json The response encoder encodes into HTTP response message status line, message header, blank line and response body.
 **/
public class HttpJsonResponseEncoder extends AbstractHttpJsonEncoder<HttpJsonResponse> {
    @Override
    protected void encode(ChannelHandlerContext ctx, HttpJsonResponse msg, List<Object> out) throws Exception {
        //code
        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);
    }

}
  • Server
package com.shu.server;

import com.shu.codec.HttpJsonRequestDecoder;
import com.shu.codec.HttpJsonResponseEncoder;
import com.shu.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 io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

import java.net.InetSocketAddress;

/**
 * @Author shu
 * @Version 1.0
 * @Date: 2022/03/07/ 21:20
 * @Description
 **/
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 {
                            //Receiving HttpJsonRequest requires a corresponding decoder
                            //ByteBuf->FullHttpRequest-> HttpJsonRequestDecoder
                            //Output HttpJsonResponse, corresponding encoder is required
                            //HttpResponseEncoder->FullHttpResponse-> HttpJsonResponseEncoder
                            ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                            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 The subscription server starts at : " + "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);
    }
}
package com.shu.server;

import com.shu.codec.HttpJsonRequest;
import com.shu.codec.HttpJsonResponse;
import com.shu.pojo.Address;
import com.shu.pojo.Order;
import com.shu.pojo.Shipping;
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.HttpVersion.HTTP_1_1;
import static io.netty.handler.codec.rtsp.RtspResponseStatuses.INTERNAL_SERVER_ERROR;
import static io.netty.handler.codec.stomp.StompHeaders.CONTENT_TYPE;

/**
 * @Author shu
 * @Version 1.0
 * @Date: 2022/03/07/ 21:20
 * @Description
 **/
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);
        // Return the message of customer service terminal
        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();
                }
            });
        }
    }



    @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("fail: " + status.toString()
                + "\r\n", CharsetUtil.UTF_8));
        response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }
}
  • Customer service end
package com.shu.client;

import com.shu.codec.HttpJsonRequestEncoder;
import com.shu.codec.HttpJsonResponseDecoder;
import com.shu.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 io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

import java.net.InetSocketAddress;

/**
 * @Author shu
 * @Version 1.0
 * @Date: 2022/03/07/ 21:22
 * @Description Http client
 **/
public class HttpJsonClient {
    public void connect(int port) throws Exception {
        // Configure client NIO thread group
        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(new LoggingHandler(LogLevel.DEBUG));
                            ch.pipeline().addLast("http-decoder",
                                    new HttpResponseDecoder());
                            ch.pipeline().addLast("http-aggregator",
                                    new HttpObjectAggregator(65536));
                            // json decoder
                            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());
                        }
                    });

            // Initiate asynchronous connection operation
            ChannelFuture f = b.connect(new InetSocketAddress(port)).sync();

            // Contemporary client link down
            f.channel().closeFuture().sync();
        } finally {
            // Exit gracefully and release NIO thread group
            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) {
                // Use default values
            }
        }
        new HttpJsonClient().connect(port);
    }
}

package com.shu.client;

import com.shu.codec.HttpJsonRequest;
import com.shu.pojo.Address;
import com.shu.pojo.Customer;
import com.shu.pojo.Order;
import com.shu.pojo.Shipping;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * @Author shu
 * @Version 1.0
 * @Date: 2022/03/07/ 21:23
 * @Description Custom processor
 **/
public class HttpJsonClientHandler extends ChannelInboundHandlerAdapter {

    /**
     * Connection just established
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Connect to the server...");
        // Send data to server
        HttpJsonRequest request = new HttpJsonRequest(null, new Order(2, new Customer(1,"ADMIN","123456"),new Address("Sichuan","Sichuan","Sichuan","Sichuan","Sichuan"), Shipping.A,125f));
        // Write data
        ctx.writeAndFlush(request);
    }

    /**
     * Read the data returned by the server
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println(msg.getClass().getName());
        System.out.println("Data received..." + msg);
    }



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

  • Observation results

First, the customer service terminal sends a request message

The server receives the request message

The server returns a response

The client accepts the response

Keywords: Java Apache Netty network Network Protocol

Added by dennismonsewicz on Tue, 08 Mar 2022 09:03:56 +0200