This is the 27th day of my participation in the August Genwen Challenge.More challenges in August

Introduction to the

In the last article, we built an HTTP server that supports Chinese and can be accessed from a browser and get the corresponding results. Although browsers are common in everyday applications, it is sometimes possible to invoke HTTP server services from a self-built client.

Today we will show you how to build an HTTP client to interact with the HTTP server.

Build the request using the client

In the previous article, we used the browser to access the server and got the results of the response, so how do we build the request on the client side?

HTTP requests in Netty can be divided into two parts, namely HttpRequest and HttpContent. HttpRequest contains only the version number and message header information, HttpContent contains the actual request content information.

But if you want to build a request, you need to include both HttpRequest and HttpContent information. Netty provides a request class called DefaultFullHttpRequest. This class contains two pieces of information and can be used directly.

Using the constructor of DefaultFullHttpRequest, we can construct an HttpRequest request as follows:

HttpRequest request = new DefaultFullHttpRequest(
                    HttpVersion.HTTP_1_1, HttpMethod.GET, uri.getRawPath(), Unpooled.EMPTY_BUFFER);
Copy the code

In the code above, the protocol we use is HTTP1.1, the method is GET, and the requested content is an empty buffer.

After building the basic request information, we may need to add additional information to the header, such as connection, accept-encoding, and cookie information.

For example, set the following information:

request.headers().set(HttpHeaderNames.HOST, host);
            request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
            request.headers().set(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP);
Copy the code

Also set cookies:

request.headers().set(
                    HttpHeaderNames.COOKIE,
                    ClientCookieEncoder.STRICT.encode(
                            new DefaultCookie("name", "flydean"),
                            new DefaultCookie("site", "www.flydean.com")));
Copy the code

Setting cookies we use is ClientCookieEncoder encode method, there are two kinds of ClientCookieEncoder encoder mode, one kind is STRICT, one kind is LAX.

In STRICT mode, the name and value of cookies are checksum sorted.

The counterpart to encoder is ClientCookieDecoder, which is used to parse cookies.

Once all of our requests are set up, we can write them to a channel.

accept-encoding

When the client writes the request, we add accept-encoding to the request header and set the value to GZIP to indicate that the client receives the encoding as GZIP.

If the server sends the encoded content of GZIP, how does the client parse it? We need to decode the GZIP encoding format.

Netty provides the HttpContentDecompressor class that decodes gzip or Deflate encoding. After decoding, both “Content-encoding” and “Content-Length” in the response header are modified.

We just need to add it to the Pipline.

The corresponding class is HttpContentCompressor, which is used to gzip or deflate the HttpMessage and HttpContent.

Therefore, HttpContentDecompressor should be added to the client pipline, and HttpContentCompressor should be added to the server pipline.

The server parses the HTTP request

The server needs a handler to parse the message requested by the client. What should the server be aware of when parsing client requests?

The first thing to notice is the issue with the client’s 100 Continue request.

There is a unique feature in HTTP called 100 (Continue) Status, which means that clients can send a request header with a “100-continue” field in the header when they are not sure whether the server will receive the request. But the request body will not be sent yet. Don’t send the request body until you receive the response from the server.

If the server receives a 100Continue request, it simply returns a confirmation:

if (HttpUtil.is100ContinueExpected(request)) {
                send100Continue(ctx);
            }

    private static void send100Continue(ChannelHandlerContext ctx) {
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, CONTINUE, Unpooled.EMPTY_BUFFER);
        ctx.write(response);
    }
Copy the code

If the request is not 100, the server is ready to return the content:

Here we use a StringBuilder to store what to return:

StringBuilder buf = new StringBuilder();
Copy the code

Why do we use StringBuf? It is possible that the server may not be able to accept all of the requests from the client at once, so it puts all of the contents to be returned into the buffer, and then returns them all together.

We can add welcome messages to the server side, and we can add various information from the client side:

buf.setLength(0); Buf.append (" Welcome to www.flydean.com\r\n"); buf.append("===================================\r\n"); buf.append("VERSION: ").append(request.protocolVersion()).append("\r\n"); buf.append("HOSTNAME: ").append(request.headers().get(HttpHeaderNames.HOST, "unknown")).append("\r\n"); buf.append("REQUEST_URI: ").append(request.uri()).append("\r\n\r\n");Copy the code

You can also add request headers to the buffer:

HttpHeaders headers = request.headers(); if (! headers.isEmpty()) { for (Entry<String, String> h: headers) { CharSequence key = h.getKey(); CharSequence value = h.getValue(); buf.append("HEADER: ").append(key).append(" = ").append(value).append("\r\n"); } buf.append("\r\n"); }Copy the code

You can add request parameter information to buffer:

QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.uri()); Map<String, List<String>> params = queryStringDecoder.parameters(); if (! params.isEmpty()) { for (Entry<String, List<String>> p: params.entrySet()) { String key = p.getKey(); List<String> vals = p.getValue(); for (String val : vals) { buf.append("PARAM: ").append(key).append(" = ").append(val).append("\r\n"); } } buf.append("\r\n"); }Copy the code

Note what happens when HttpContent is read. If the message read is HttpContent, add the contents of the content to buffer:

if (msg instanceof HttpContent) {
            HttpContent httpContent = (HttpContent) msg;

            ByteBuf content = httpContent.content();
            if (content.isReadable()) {
                buf.append("CONTENT: ");
                buf.append(content.toString(CharsetUtil.UTF_8));
                buf.append("\r\n");
                appendDecoderResult(buf, request);
            }
Copy the code

So how do you tell if a request is over? Netty provides a class called LastHttpContent, which represents the last part of the message. After receiving this part of the message, we can determine that an HTTP request has been completed and can officially return the message:

if (msg instanceof LastHttpContent) {
                log.info("LastHttpContent:{}",msg);
                buf.append("END OF CONTENT\r\n");
Copy the code

To write back to a channel, we also need to build a DefaultFullHttpResponse, which is constructed using buffer:

FullHttpResponse response = new DefaultFullHttpResponse(
                HTTP_1_1, currentObj.decoderResult().isSuccess()? OK : BAD_REQUEST,
                Unpooled.copiedBuffer(buf.toString(), CharsetUtil.UTF_8));
Copy the code

Then add some necessary header information and call ctx.write to write back.

conclusion

This article introduces how to construct HTTP request in client, and explains in detail the HTTP server to HTTP request parsing process.

Learn -netty4 for an example of this article

This article is available at www.flydean.com/19-netty-ht…

The most popular interpretation, the most profound dry goods, the most concise tutorial, many tips you didn’t know waiting for you to discover!

Welcome to pay attention to my public number: “procedures those things”, understand technology, more understand you!