使用的netty版本为:netty-all-4.1.34.Final
关键handler
pipeline.addLast(new HttpClientCodec());
pipeline.addLast(new HttpObjectAggregator(10 * 1024 * 1024)); // 10MB
Get请求
public T get(String path, Map params) {
QueryStringEncoder encoder = new QueryStringEncoder(path);
params.forEach((name, value) -> {
encoder.addParam(name, value);
});
try {
String uri = encoder.toUri().toASCIIString();
FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri);
request.headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
request.headers().set(HttpHeaderNames.CONTENT_LENGTH, request.content().readableBytes());
connect().writeAndFlush(request);
Channel channel = connect();
DefaultPromise promise = new DefaultPromise(channel.eventLoop());
channel.attr(AttributeKey.valueOf("promise")).set(promise);
channel.writeAndFlush(request);
promise.await(60000); // 超时时间,后面解释
if (promise.isSuccess())
return promise.get();
else {
try {
if (channel.isOpen())
channel.close();
} catch (Exception ignore) {
}
return null;
}
} catch (Exception e) {
throw new RuntimeException("get请求异常");
}
}
Post请求
public T post(String path, Map params, Class resultType, int timeout) {
FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.POST, path);
try {
// HttpPostRequestEncoder 因为第二个参数 multipart 为 true,故content-type为:multipart/form-data
// 如果以普通表单形式发送,则第二个参数 multipart 设为 false 即可,content-type则为:application/x-www-form-urlencoded
HttpPostRequestEncoder encoder = new HttpPostRequestEncoder(request, true);
params.forEach((name, value) -> {
try {
encoder.addBodyAttribute(name, value);
} catch (ErrorDataEncoderException e) {
throw new RuntimeException(e);
}
// 文件上传使用如下方法
// encoder.addBodyFileUpload(name, file, contentType, isText);
});
// 因为 multipart 为true,默认处理会chunked,故返回的request无法进行直接发送,因为实际的content区为空,需要手动填充content
encoder.finalizeRequest();
// 如果multipart为false,则这一段不需要 start,multipart 为false 时,finalizeRequest 会自己填充content,
// 若构造的httprequest并非FullHttpRequest,则需要接收finalizeRequest的返回值来进行发送,而不是发送当前request
ByteBuf content = encoder.readChunk(PooledByteBufAllocator.DEFAULT).content();
request.content().clear().writeBytes(content);
content.release();
request.headers().set(HttpHeaderNames.HOST, getHost());
request.headers().set(HttpHeaderNames.CONTENT_LENGTH, request.content().readableBytes());
HttpUtil.setTransferEncodingChunked(request, false);
// 如果multipart为false,则这一段不需要 end
Channel channel = connect();
DefaultPromise promise = new DefaultPromise(channel.eventLoop());
channel.attr(AttributeKey.valueOf("generics")).set(resultType);
channel.attr(AttributeKey.valueOf("promise")).set(promise);
channel.writeAndFlush(request);
promise.await(timeout);
if (promise.isSuccess())
return promise.get();
else {
try {
if (channel.isOpen())
channel.close();
} catch (Exception ignore) {
}
return null;
}
} catch (Exception e) {
throw new RuntimeException("post请求异常");
}
}
解释上面超时等待逻辑
netty使用nio,消息进出都是异步的,故想要实现普通httprequest的同步请求等待响应需要自己动手,以上为自己实现的逻辑
nettty自4.x版本,attr不再由各个ChannelHandlerContext 单独管理,而是统一在channel里管理,我们想实现同步操作就用这个来做文章。
在发出请求时,创建一个 promise (参考future)
DefaultPromise promise = new DefaultPromise(channel.eventLoop());
// 如果不需要携带返回值,则可直接使用如下
// channel.newPromise()
然后将其塞入 channel 的 attr 内
channel.attr(AttributeKey.valueOf("promise")).set(promise);
然后进行发送请求操作,发送之后让 promise 进行等待
// 设定超时时间 timeout (ms)
promise.await(timeout);
if (promise.isSuccess())
return promise.get();
else {
try {
if (channel.isOpen())
channel.close();
} catch (Exception ignore) {
}
return null;
}
发送请求这部分就完成了,然后看看什么时候进行通知 promise 完成。
如下Handler,用来处理response,当收到response 的时候,则从 channel 的 attr 里取出 promise,然后 setSuccess,参数为需要携带的值,当此处 setSuccess 后,请求发送端的等待会结束,执行后续正常步骤,至此,就实现了同步的 httprequest
@Sharable
public class HttpClientResponseHandler extends SimpleChannelInboundHandler {
private Gson gson = new Gson();
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse msg) throws Exception {
String json = msg.content().toString(HttpClient.UTF_8);
try {
Channel channel = ctx.channel();
Class> resultType = (Class>) channel.attr(AttributeKey.valueOf("generics")).get();
Object result = null;
if (resultType == String.class) {
result = json;
} else {
result = gson.fromJson(json, resultType);
}
@SuppressWarnings("unchecked")
DefaultPromise
注:没有特殊需求,并不推荐使用