package de.jpaw.bonaparte.sock;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.jpaw.bonaparte.core.BonaPortable;
import de.jpaw.bonaparte.util.IMarshaller;
import de.jpaw.bonaparte.util.impl.RecordMarshallerBonaparte;
import de.jpaw.util.ByteArray;
import de.jpaw.util.ByteBuilder;
/**
* Client connection via http. This class provides a request / response functionality implemented via http POST.
* The http authorization header can be modifed during the lifetime of an instance.
* The marshaller can be changed as well, this is however usually not desired.
*
* @author mbi
*
*/
public class HttpPostClient implements INetworkDialog {
private static final Logger LOGGER = LoggerFactory.getLogger(HttpPostClient.class);
protected String baseUrl;
private final boolean addVariableUrlPath;
private final boolean logSizes;
private final boolean logText;
private final boolean logHex;
private URL cachedUrl = null;
protected IMarshaller marshaller;
protected String authentication = null;
public HttpPostClient(String baseUrl, boolean addVariableUrlPath, boolean logSizes, boolean logText, boolean logHex, IMarshaller initialMarshaller) {
this.baseUrl = baseUrl;
this.addVariableUrlPath = addVariableUrlPath;
this.logSizes = logSizes;
this.logText = logText;
this.logHex = logHex;
this.marshaller = initialMarshaller;
}
public HttpPostClient(String baseUrl) {
this(baseUrl, false, false, false, false, new RecordMarshallerBonaparte());
}
public void setMarshaller(IMarshaller marshaller) {
this.marshaller = marshaller;
}
public void setAuthentication(String authentication) {
this.authentication = authentication;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
cachedUrl = null; // reset cached converted URL if changing the base path!
}
// properties can be set in their own method to allow overriding it
protected void setRequestProperties(HttpURLConnection connection) {
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setInstanceFollowRedirects(false);
connection.setConnectTimeout(5000);
connection.setRequestProperty("Content-Type", marshaller.getContentType());
connection.setRequestProperty("Accept", marshaller.getContentType());
connection.setRequestProperty("Charset", "utf-8");
connection.setRequestProperty("Accept-Charset", "utf-8");
connection.setUseCaches(false);
}
protected void requestLogger(String pqon, ByteArray serializedRequest) {
if (logSizes)
LOGGER.info("{} serialized as {} bytes for MIME type {}", pqon, serializedRequest.length(), marshaller.getContentType());
if (logText)
LOGGER.info("Request is <{}>", serializedRequest.toString());
if (logHex)
LOGGER.info(serializedRequest.hexdump(0, 0));
}
protected void responseLogger(ByteBuilder serializedResponse) {
if (logSizes)
LOGGER.info("retrieved {} bytes response", serializedResponse.length());
if (logText)
LOGGER.info("Response is <{}>", serializedResponse.toString());
if (logHex)
LOGGER.info(serializedResponse.hexdump(0, 0));
}
protected BonaPortable errorReturn(String requestPqon, int returnCode, String statusMessage) throws Exception {
LOGGER.warn("response for {} is HTTP {} ({})", requestPqon, returnCode, statusMessage);
return null;
}
/** Execute the request / response dialog. */
@Override
public BonaPortable doIO(BonaPortable request) throws Exception {
ByteArray serializedRequest = ByteArray.ZERO_BYTE_ARRAY;
String requestPqon = "LOGOUT";
if (request != null) {
serializedRequest = marshaller.marshal(request);
requestPqon = request.ret$PQON();
}
URL url = null;
if (addVariableUrlPath && request != null) {
String variablePath = request.ret$BonaPortableClass().getProperty("path");
if (variablePath != null) {
url = new URL(baseUrl + "/" + variablePath);
}
}
if (url == null) {
if (cachedUrl == null)
cachedUrl = new URL(baseUrl);
url = cachedUrl;
}
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
setRequestProperties(connection);
if (authentication != null)
connection.setRequestProperty("Authorization", authentication);
connection.setRequestProperty("Content-Length", "" + serializedRequest.length());
requestLogger(requestPqon, serializedRequest);
// write the request
OutputStream wr = connection.getOutputStream();
serializedRequest.toOutputStream(wr);
wr.flush();
// according to https://www.tbray.org/ongoing/When/201x/2012/01/17/HttpURLConnection the status should be available
// after getInputStream for GET
// before for POST
// in either case, swapping the order would cause a nasty IOException!
int returnCode = connection.getResponseCode();
String statusMessage = connection.getResponseMessage();
if (returnCode != HttpURLConnection.HTTP_OK) {
return errorReturn(requestPqon, returnCode, statusMessage);
}
// retrieve the response
InputStream is = connection.getInputStream();
ByteBuilder serializedResponse = new ByteBuilder();
serializedResponse.readFromInputStream(is, 0);
is.close();
wr.close();
responseLogger(serializedResponse);
return serializedResponse.length() == 0 ? null : marshaller.unmarshal(serializedResponse);
}
}