Introduction
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 daily applications, sometimes we may also call HTTP server services from a self-built client.
Today I will show you how to build an HTTP client to interact with the HTTP server.
Use the client to build the request
In the previous article, we used a browser to access the server and got the response result, so how to construct the request on the client?
The HTTP request in netty can be divided into two parts, namely HttpRequest and HttpContent. Where HttpRequest only contains the version number of the request and the information of the message header, HttpContent contains the real request content information.
But if you want to construct a request, you need to include both HttpRequest and HttpContent information. Netty provides a request class called DefaultFullHttpRequest, which contains two parts of information at the same time, which 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);
In the above code, the protocol we use is HTTP1.1, the method is GET, and the requested content is an empty buffer.
After constructing the basic request information, we may need to add additional information in 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);
Also set cookies:
request.headers().set(
HttpHeaderNames.COOKIE,
ClientCookieEncoder.STRICT.encode(
new DefaultCookie("name", "flydean"),
new DefaultCookie("site", "www.flydean.com")));
To set cookies, we use the ClientCookieEncoder.encode method. ClientCookieEncoder has two encoder modes, one is STRICT and the other is LAX.
In STRICT mode, the name and value of the cookie will be checked and sorted.
Corresponding to the encoder is ClientCookieDecoder, which is used to parse the cookie.
After setting up all our requests, we can write them to the channel.
accept-encoding
When the client writes the request, we add accept-encoding to the request header and set its value to GZIP, indicating that the encoding method received by the client is 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, which can decode the encoding in gzip or deflate format. After decoding, the "Content-Encoding" and "Content-Length" in the response header will be modified at the same time.
We just need to add it to pipline.
Its corresponding class is HttpContentCompressor, which is used for gzip or deflate encoding of HttpMessage and HttpContent.
So HttpContentDecompressor should be added to the client's pipline, and HttpContentCompressor should be added to the server's pipline.
The server parses the HTTP request
The server needs a handler to parse the message requested by the client. For the server, what issues should be paid attention to when parsing the client's request?
The first thing to pay attention to is the problem of the client's 100 Continue request.
There is a unique feature in HTTP called 100 (Continue) Status, which means that when the client is not sure whether the server will receive the request, it can send a request header and add a "100-continue" to this header. Field, but the request body is not sent yet. The request body will not be sent until the response from the server is received.
If the server receives a 100Continue request, just return the confirmation directly:
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);
}
If it is not 100 requests, the server can prepare the content to be returned:
Here a StringBuilder is used to store the content to be returned:
StringBuilder buf = new StringBuilder();
Why use StringBuf? It is because it is possible that the server cannot completely accept the client's request at one time, so put all the content to be returned in the buffer, and then return all the content after it is accepted.
We can add a welcome message to the server, and we can add various information obtained from the client:
buf.setLength(0);
buf.append("欢迎来到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");
You can also add request header information 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");
}
You can add request parameter information to the 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");
}
It should be noted that the processing method when the HttpContent is read. If the read message is HttpContent, add the content of content to the 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);
}
So how to judge whether a request is over? Netty provides a class called LastHttpContent. This class 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");
To write back to the channel, you also need to build a DefaultFullHttpResponse, here we use buffer to build:
FullHttpResponse response = new DefaultFullHttpResponse(
HTTP_1_1, currentObj.decoderResult().isSuccess()? OK : BAD_REQUEST,
Unpooled.copiedBuffer(buf.toString(), CharsetUtil.UTF_8));
Then add some necessary header information to call ctx.write to write back.
Summarize
This article introduces how to construct HTTP requests on the client, and explains in detail the process of parsing HTTP requests by the HTTP server.
For the examples in this article, please refer to: learn-netty4
This article has been included in http://www.flydean.com/19-netty-http-client-request-2/
The most popular interpretation, the most profound dry goods, the most concise tutorial, and many tips you don't know are waiting for you to discover!
Welcome to pay attention to my official account: "Program those things", know technology, know you better!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。