org.yamcs.api.rest.HttpClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of yamcs-api Show documentation
Show all versions of yamcs-api Show documentation
Used by external clients to communicate with Yamcs
package org.yamcs.api.rest;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import org.yamcs.api.MediaType;
import org.yamcs.api.YamcsApiException;
import org.yamcs.api.YamcsApiException.RestExceptionData;
import org.yamcs.protobuf.Table;
import org.yamcs.protobuf.Web.RestExceptionMessage;
import org.yamcs.security.AuthenticationToken;
import org.yamcs.security.UsernamePasswordToken;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.google.protobuf.Extension;
import com.google.protobuf.ExtensionRegistry;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
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.DefaultFullHttpRequest;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpContentDecompressor;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.cookie.ClientCookieEncoder;
import io.netty.handler.codec.http.cookie.Cookie;
public class HttpClient {
MediaType sendMediaType = MediaType.PROTOBUF;
MediaType acceptMediaType = MediaType.PROTOBUF;
EventLoopGroup group;
private List cookies;
private int maxResponseLength = 1024 * 1024;// max length of the expected response
// extensions for the RestExceptionMessage
static ExtensionRegistry exceptionRegistry = ExtensionRegistry.newInstance();
static Set> exceptionExtensions = new HashSet<>(1);
static {
exceptionRegistry.add(Table.rowsLoaded);
exceptionExtensions.add(Table.rowsLoaded);
}
public CompletableFuture doAsyncRequest(String url, HttpMethod httpMethod, byte[] body,
AuthenticationToken authToken) throws URISyntaxException {
return doAsyncRequest(url, httpMethod, body, authToken, null);
}
public CompletableFuture doAsyncRequest(String url, HttpMethod httpMethod, byte[] body,
AuthenticationToken authToken, HttpHeaders extraHeaders) throws URISyntaxException {
URI uri = new URI(url);
HttpObjectAggregator aggregator = new HttpObjectAggregator(maxResponseLength);
CompletableFuture cf = new CompletableFuture<>();
ResponseHandler respHandler = new ResponseHandler(cf);
HttpRequest request = setupRequest(uri, httpMethod, body, authToken);
if (extraHeaders != null) {
request.headers().add(extraHeaders);
}
ChannelFuture chf = setupChannel(uri, aggregator, respHandler);
chf.addListener(f -> {
if (!f.isSuccess()) {
cf.completeExceptionally(f.cause());
return;
}
chf.channel().writeAndFlush(request);
});
return cf;
}
public CompletableFuture doBulkSendRequest(String url, HttpMethod httpMethod,
AuthenticationToken authToken) throws URISyntaxException {
URI uri = new URI(url);
CompletableFuture cf = new CompletableFuture<>();
BulkRestDataSender.ContinuationHandler chandler = new BulkRestDataSender.ContinuationHandler(cf);
ChannelFuture chf = setupChannel(uri, chandler);
HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, httpMethod, getPathWithQuery(uri));
fillInHeaders(request, uri, authToken);
request.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
HttpUtil.set100ContinueExpected(request, true);
chf.addListener(f -> {
if (!f.isSuccess()) {
cf.completeExceptionally(f.cause());
return;
}
chf.channel().writeAndFlush(request);
});
return cf;
}
static YamcsApiException decodeException(HttpObject httpObj) throws IOException {
YamcsApiException exception;
if (httpObj instanceof HttpResponse) {
if (httpObj instanceof FullHttpResponse) {
FullHttpResponse fullResp = (FullHttpResponse) httpObj;
byte[] data = getByteArray(fullResp.content());
String contentType = fullResp.headers().get(HttpHeaderNames.CONTENT_TYPE);
// We could probably simplify this by using Any type once using protobuf3
// Reason for the complexity is that 'extensions' are not supported by JsonFormat
// (note that extensions were removed from proto3)
if (MediaType.JSON.is(contentType)) {
Map obj = jsonToMap(new String(data));
String type = (String) obj.get("type");
String message = (String) obj.get("msg");
RestExceptionData excData = new RestExceptionData(type, message);
for (Entry entry : obj.entrySet()) {
if (!"type".equals(entry.getKey()) && !"msg".equals(entry.getKey())) {
excData.addDetail(entry.getKey(), entry.getValue());
}
}
exception = new YamcsApiException(excData);
} else if (MediaType.PROTOBUF.is(contentType)) {
RestExceptionMessage msg = RestExceptionMessage.parseFrom(data, exceptionRegistry);
RestExceptionData excData = new RestExceptionData(msg.getType(), msg.getMsg());
for (Extension extension : exceptionExtensions) {
if (msg.hasExtension(extension)) {
String key = extension.getDescriptor().getJsonName();
excData.addDetail(key, msg.getExtension(extension));
}
}
exception = new YamcsApiException(excData);
} else {
exception = new YamcsApiException(fullResp.status() + ": " + new String(data));
}
} else {
exception = getInvalidHttpResponseException(((HttpResponse) httpObj).status().toString());
}
} else {
exception = getInvalidHttpResponseException(httpObj.toString());
}
return exception;
}
private static Map jsonToMap(String json) {
Type gsonType = new TypeToken