ConnettoreHTTP.java
/*
* GovWay - A customizable API Gateway
* https://govway.org
*
* Copyright (c) 2005-2024 Link.it srl (https://link.it).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3, as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openspcoop2.pdd.core.connettori;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
import org.apache.commons.lang.StringUtils;
import org.openspcoop2.core.config.ResponseCachingConfigurazione;
import org.openspcoop2.core.config.constants.CostantiConfigurazione;
import org.openspcoop2.core.constants.CostantiConnettori;
import org.openspcoop2.core.constants.TipiConnettore;
import org.openspcoop2.core.constants.TransferLengthModes;
import org.openspcoop2.core.transazioni.constants.TipoMessaggio;
import org.openspcoop2.message.OpenSPCoop2SoapMessage;
import org.openspcoop2.message.constants.Costanti;
import org.openspcoop2.message.constants.MessageType;
import org.openspcoop2.message.soap.TunnelSoapUtils;
import org.openspcoop2.pdd.mdb.ConsegnaContenutiApplicativi;
import org.openspcoop2.utils.NameValue;
import org.openspcoop2.utils.Utilities;
import org.openspcoop2.utils.UtilsException;
import org.openspcoop2.utils.date.DateManager;
import org.openspcoop2.utils.io.Base64Utilities;
import org.openspcoop2.utils.io.DumpByteArrayOutputStream;
import org.openspcoop2.utils.transport.TransportUtils;
import org.openspcoop2.utils.transport.http.HttpBodyParameters;
import org.openspcoop2.utils.transport.http.HttpConstants;
import org.openspcoop2.utils.transport.http.HttpUtilities;
import org.openspcoop2.utils.transport.http.RFC2047Utilities;
import org.openspcoop2.utils.transport.http.SSLUtilities;
import org.openspcoop2.utils.transport.http.WrappedLogSSLSocketFactory;
/**
* Classe utilizzata per effettuare consegne di messaggi Soap, attraverso
* l'invocazione di un server http.
*
*
* @author Poli Andrea (apoli@link.it)
* @author $Author$
* @version $Rev$, $Date$
*/
public class ConnettoreHTTP extends ConnettoreBaseHTTP {
// NOTA: HttpMethod PATCH/LINK/UNLINK funziona solo con java 8
// Con java7 si ottiene l'errore:
// Caused by: java.net.ProtocolException: HTTP method PATCH doesn't support output
// at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1081)
/* ******** F I E L D S P R I V A T I ******** */
/** Proxy Configuration */
protected Proxy.Type proxyType = null;
protected String proxyHostname = null;
protected int proxyPort;
protected String proxyUsername;
protected String proxyPassword;
/** Redirect */
protected boolean followRedirects = false;
protected String routeRedirect = null;
protected int numberRedirect = 0;
protected int maxNumberRedirects = 5;
protected String redirectLocation = null;
protected String originalAbsolutePrefixForRelativeRedirectLocation = null;
/** TransferMode */
protected TransferLengthModes tlm = null;
protected int chunkLength = -1;
/** Connessione */
protected HttpURLConnection httpConn = null;
/** KeepAlive */
protected boolean keepAlive = false;
/* Costruttori */
public ConnettoreHTTP(){
this.connettoreHttps = false;
}
public ConnettoreHTTP(boolean https){
this.connettoreHttps = https;
}
/* ******** METODI ******** */
@Override
protected boolean initializePreSend(ResponseCachingConfigurazione responseCachingConfig, ConnettoreMsg request) {
return this.initialize(request, true, responseCachingConfig);
}
@Override
protected boolean send(ConnettoreMsg request) {
// HTTPS
try{
this.setSSLContext();
}catch(Exception e){
this.eccezioneProcessamento = e;
this.logger.error("[HTTPS error]"+ this.readExceptionMessageFromException(e),e);
this.errore = "[HTTPS error]"+ this.readExceptionMessageFromException(e);
return false;
}
// Proxy
if(this.properties.get(CostantiConnettori.CONNETTORE_HTTP_PROXY_TYPE)!=null){
String tipo = this.properties.get(CostantiConnettori.CONNETTORE_HTTP_PROXY_TYPE).trim();
if(CostantiConnettori.CONNETTORE_HTTP_PROXY_TYPE_VALUE_HTTP.equals(tipo)){
this.proxyType = Proxy.Type.HTTP;
}
else if(CostantiConnettori.CONNETTORE_HTTP_PROXY_TYPE_VALUE_HTTPS.equals(tipo)){
this.proxyType = Proxy.Type.HTTP;
}
else{
this.errore = "Proprieta' '"+CostantiConnettori.CONNETTORE_HTTP_PROXY_TYPE
+"' non corretta. Impostato un tipo sconosciuto ["+tipo+"] (valori ammessi: "+CostantiConnettori.CONNETTORE_HTTP_PROXY_TYPE_VALUE_HTTP
+","+CostantiConnettori.CONNETTORE_HTTP_PROXY_TYPE_VALUE_HTTPS+")";
return false;
}
this.proxyHostname = this.properties.get(CostantiConnettori.CONNETTORE_HTTP_PROXY_HOSTNAME);
if(this.proxyHostname!=null){
this.proxyHostname = this.proxyHostname.trim();
}else{
this.errore = "Proprieta' '"+CostantiConnettori.CONNETTORE_HTTP_PROXY_HOSTNAME+
"' non impostata, obbligatoria in presenza della proprietà '"+CostantiConnettori.CONNETTORE_HTTP_PROXY_TYPE+"'";
return false;
}
String proxyPortTmp = this.properties.get(CostantiConnettori.CONNETTORE_HTTP_PROXY_PORT);
if(proxyPortTmp!=null){
proxyPortTmp = proxyPortTmp.trim();
}else{
this.errore = "Proprieta' '"+CostantiConnettori.CONNETTORE_HTTP_PROXY_PORT+
"' non impostata, obbligatoria in presenza della proprietà '"+CostantiConnettori.CONNETTORE_HTTP_PROXY_TYPE+"'";
return false;
}
try{
this.proxyPort = Integer.parseInt(proxyPortTmp);
}catch(Exception e){
this.errore = "Proprieta' '"+CostantiConnettori.CONNETTORE_HTTP_PROXY_PORT+"' non corretta: "+this.readExceptionMessageFromException(e);
return false;
}
this.proxyUsername = this.properties.get(CostantiConnettori.CONNETTORE_HTTP_PROXY_USERNAME);
if(this.proxyUsername!=null){
this.proxyUsername = this.proxyUsername.trim();
}
this.proxyPassword = this.properties.get(CostantiConnettori.CONNETTORE_HTTP_PROXY_PASSWORD);
if(this.proxyPassword!=null){
this.proxyPassword = this.proxyPassword.trim();
}else{
if(this.proxyUsername!=null){
this.errore = "Proprieta' '"+CostantiConnettori.CONNETTORE_HTTP_PROXY_PASSWORD
+"' non impostata, obbligatoria in presenza della proprietà '"+CostantiConnettori.CONNETTORE_HTTP_PROXY_USERNAME+"'";
return false;
}
}
}
// TransferMode
if(ConsegnaContenutiApplicativi.ID_MODULO.equals(this.idModulo)){
this.tlm = this.openspcoopProperties.getTransferLengthModes_consegnaContenutiApplicativi();
this.chunkLength = this.openspcoopProperties.getChunkLength_consegnaContenutiApplicativi();
}
else{
// InoltroBuste e InoltroRisposte
this.tlm = this.openspcoopProperties.getTransferLengthModes_inoltroBuste();
this.chunkLength = this.openspcoopProperties.getChunkLength_inoltroBuste();
}
String tlmTmp = this.properties.get(CostantiConnettori.CONNETTORE_HTTP_DATA_TRANSFER_MODE);
if(tlmTmp!=null){
tlmTmp = tlmTmp.trim();
try{
this.tlm = TransferLengthModes.getTransferLengthModes(tlmTmp);
}catch(Exception e){
this.errore = "Proprieta' '"+CostantiConnettori.CONNETTORE_HTTP_DATA_TRANSFER_MODE
+"' non impostata correttamente: "+e.getMessage();
return false;
}
}
if(TransferLengthModes.TRANSFER_ENCODING_CHUNKED.equals(this.tlm)){
tlmTmp = this.properties.get(CostantiConnettori.CONNETTORE_HTTP_DATA_TRANSFER_MODE_CHUNK_SIZE);
//this.log.info("PROPERTY! ("+redirectTmp+")");
if(tlmTmp!=null){
tlmTmp = tlmTmp.trim();
this.chunkLength = Integer.parseInt(tlmTmp);
}
}
// Redirect
if(ConsegnaContenutiApplicativi.ID_MODULO.equals(this.idModulo)){
if(this.isSoap) {
this.followRedirects = this.openspcoopProperties.isFollowRedirects_consegnaContenutiApplicativi_soap();
}else {
this.followRedirects = this.openspcoopProperties.isFollowRedirects_consegnaContenutiApplicativi_rest();
}
this.maxNumberRedirects = this.openspcoopProperties.getFollowRedirectsMaxHop_consegnaContenutiApplicativi();
}
else{
// InoltroBuste e InoltroRisposte
if(this.isSoap) {
this.followRedirects = this.openspcoopProperties.isFollowRedirects_inoltroBuste_soap();
}
else {
this.followRedirects = this.openspcoopProperties.isFollowRedirects_inoltroBuste_rest();
}
this.maxNumberRedirects = this.openspcoopProperties.getFollowRedirectsMaxHop_inoltroBuste();
}
String redirectTmp = this.properties.get(CostantiConnettori.CONNETTORE_HTTP_REDIRECT_FOLLOW);
if(redirectTmp!=null){
redirectTmp = redirectTmp.trim();
this.followRedirects = "true".equalsIgnoreCase(redirectTmp) || CostantiConfigurazione.ABILITATO.getValue().equalsIgnoreCase(redirectTmp);
}
//this.log.info("FOLLOW! ("+this.followRedirects+")");
if(this.followRedirects){
redirectTmp = this.properties.get(CostantiConnettori.CONNETTORE_HTTP_REDIRECT_MAX_HOP);
//this.log.info("PROPERTY! ("+redirectTmp+")");
if(redirectTmp!=null){
redirectTmp = redirectTmp.trim();
this.maxNumberRedirects = Integer.parseInt(redirectTmp);
}
redirectTmp = this.properties.get(CostantiConnettori.CONNETTORE_HTTP_REDIRECT_NUMBER);
//this.log.info("PROPERTY! ("+redirectTmp+")");
if(redirectTmp!=null){
redirectTmp = redirectTmp.trim();
this.numberRedirect = Integer.parseInt(redirectTmp);
}
redirectTmp = this.properties.get(CostantiConnettori.CONNETTORE_HTTP_REDIRECT_ROUTE);
//this.log.info("PROPERTY! ("+redirectTmp+")");
if(redirectTmp!=null){
redirectTmp = redirectTmp.trim();
this.routeRedirect = redirectTmp;
}
}
return sendHTTP(request);
}
/**
* Si occupa di effettuare la consegna HTTP_POST (sbustando il messaggio SOAP).
* Si aspetta di ricevere una risposta non sbustata.
*
* @return true in caso di consegna con successo, false altrimenti
*
*/
protected boolean sendHTTP(ConnettoreMsg request){
int connectionTimeout = -1;
boolean connectionTimeoutConfigurazioneGlobale = true;
int readConnectionTimeout = -1;
boolean readConnectionTimeoutConfigurazioneGlobale = true;
try{
// Creazione URL
if(this.debug)
this.logger.debug("Creazione URL...");
this.buildLocation();
if(this.debug)
this.logger.debug("Creazione URL ["+this.location+"]...");
URL url = new URL( this.location );
// Collezione header di trasporto per dump
Map<String, List<String>> propertiesTrasportoDebug = null;
if(this.isDumpBinarioRichiesta()) {
propertiesTrasportoDebug = new HashMap<>();
}
// Creazione Connessione
URLConnection connection = null;
if(this.proxyType==null){
if(this.debug)
this.logger.info("Creazione connessione alla URL ["+this.location+"]...",false);
connection = url.openConnection();
}
else{
if(this.debug)
this.logger.info("Creazione connessione alla URL ["+this.location+"] (via proxy "+
this.proxyHostname+":"+this.proxyPort+") (username["+this.proxyUsername+"] password["+this.proxyPassword+"])...",false);
if(this.proxyUsername!=null){
//The problem with the 2nd code is that it sets a new default Authenticator and
// I don't want to do that, because this proxy is only used by a part of the application
// and a different part of the application could be using a different proxy.
// Vedi articolo: http://stackoverflow.com/questions/34877470/basic-proxy-authentification-for-https-urls-returns-http-1-0-407-proxy-authentic
// Authenticator.setDefault(new HttpAuthenticator(this.proxyUsername, this.proxyPassword));
// Soluzione attuale:
// Dopo aver instaurato la connesione, più sotto nel codice, viene creato l'header Proxy-Authorization
// NOTA: Works for HTTP only! Doesn't work for HTTPS!
}
Proxy proxy = new Proxy(this.proxyType, new InetSocketAddress(this.proxyHostname, this.proxyPort));
connection = url.openConnection(proxy);
}
this.httpConn = (HttpURLConnection) connection;
// Imposta Contesto SSL se attivo
if(this.sslContextProperties!=null){
HttpsURLConnection httpsConn = (HttpsURLConnection) this.httpConn;
SSLSocketFactory sslSocketFactory = buildSSLContextFactory();
if(this.debug) {
String clientCertificateConfigurated = this.sslContextProperties.getKeyStoreLocation();
sslSocketFactory = new WrappedLogSSLSocketFactory(sslSocketFactory,
this.logger.getLogger(), this.logger.buildMsg(""),
clientCertificateConfigurated);
}
httpsConn.setSSLSocketFactory(sslSocketFactory);
StringBuilder bfLog = new StringBuilder();
HostnameVerifier hostnameVerifier = SSLUtilities.generateHostnameVerifier(this.sslContextProperties, bfLog,
this.logger.getLogger(), this.loader);
if(this.debug)
this.logger.debug(bfLog.toString());
if(hostnameVerifier!=null){
httpsConn.setHostnameVerifier(hostnameVerifier);
}
}
else {
if(this.debug && (this.httpConn instanceof HttpsURLConnection)) {
HttpsURLConnection httpsConn = (HttpsURLConnection) this.httpConn;
if(httpsConn.getSSLSocketFactory()!=null) {
SSLSocketFactory sslSocketFactory = httpsConn.getSSLSocketFactory();
String clientCertificateConfigurated = SSLUtilities.getJvmHttpsClientCertificateConfigurated();
sslSocketFactory = new WrappedLogSSLSocketFactory(sslSocketFactory,
this.logger.getLogger(), this.logger.buildMsg(""),
clientCertificateConfigurated);
httpsConn.setSSLSocketFactory(sslSocketFactory);
}
}
}
// Gestione automatica del redirect
// The HttpURLConnection‘s follow redirect is just an indicator, in fact it won’t help you to do the “real” http redirection, you still need to handle it manually.
/*
if(followRedirect){
this.httpConn.setInstanceFollowRedirects(true);
}
*/
// Deve essere impostato a false, altrimenti nel caso si intenda leggere gli header o l'input stream di un 302
// si ottiene il seguente errore:
// java.net.HttpRetryException: cannot retry due to redirection, in streaming mode
this.httpConn.setInstanceFollowRedirects(false);
// Tipologia di servizio
MessageType requestMessageType = this.requestMsg.getMessageType();
OpenSPCoop2SoapMessage soapMessageRequest = null;
if(this.debug)
this.logger.debug("Tipologia Servizio: "+this.requestMsg.getServiceBinding());
if(this.isSoap){
soapMessageRequest = this.requestMsg.castAsSoap();
}
// Alcune implementazioni richiedono di aggiornare il Content-Type
this.requestMsg.updateContentType();
// Proxy Authentication BASIC
if(this.proxyType!=null && this.proxyUsername!=null){
if(this.debug)
this.logger.debug("Impostazione autenticazione per proxy (username["+this.proxyUsername+"] password["+this.proxyPassword+"]) ...");
if(this.proxyUsername!=null && this.proxyPassword!=null){
String authentication = this.proxyUsername + ":" + this.proxyPassword;
authentication = HttpConstants.AUTHORIZATION_PREFIX_BASIC + Base64Utilities.encodeAsString(authentication.getBytes());
setRequestHeader(HttpConstants.PROXY_AUTHORIZATION,authentication, propertiesTrasportoDebug);
if(this.debug)
this.logger.info("Impostazione autenticazione per proxy (username["+this.proxyUsername+"] password["+this.proxyPassword+"]) ["+authentication+"]",false);
}
}
// Impostazione Content-Type della Spedizione su HTTP
String contentTypeRichiesta = null;
if(this.debug)
this.logger.debug("Impostazione content type...");
if(this.isSoap){
if(this.sbustamentoSoap && soapMessageRequest.countAttachments()>0 && TunnelSoapUtils.isTunnelOpenSPCoopSoap(soapMessageRequest)){
contentTypeRichiesta = TunnelSoapUtils.getContentTypeTunnelOpenSPCoopSoap(soapMessageRequest.getSOAPBody());
}else{
contentTypeRichiesta = this.requestMsg.getContentType();
}
if(contentTypeRichiesta==null){
throw new Exception("Content-Type del messaggio da spedire non definito");
}
}
else{
contentTypeRichiesta = this.requestMsg.getContentType();
// Content-Type non obbligatorio in REST
}
if(this.debug)
this.logger.info("Impostazione http Content-Type ["+contentTypeRichiesta+"]",false);
if(contentTypeRichiesta!=null){
setRequestHeader(HttpConstants.CONTENT_TYPE,contentTypeRichiesta, propertiesTrasportoDebug);
}
// HttpMethod
if(this.httpMethod==null){
throw new Exception("HttpRequestMethod non definito");
}
// Impostazione transfer-length
if(this.debug)
this.logger.debug("Impostazione transfer-length...");
if(TransferLengthModes.TRANSFER_ENCODING_CHUNKED.equals(this.tlm)){
HttpUtilities.setChunkedStreamingMode(this.httpConn, this.chunkLength, this.httpMethod, contentTypeRichiesta);
//this.httpConn.setChunkedStreamingMode(chunkLength);
}
if(this.debug)
this.logger.info("Impostazione transfer-length effettuata (chunkLength:"+this.chunkLength+"): "+this.tlm,false);
// Impostazione timeout
if(this.debug)
this.logger.debug("Impostazione timeout...");
if(this.properties.get(CostantiConnettori.CONNETTORE_CONNECTION_TIMEOUT)!=null){
try{
connectionTimeout = Integer.parseInt(this.properties.get(CostantiConnettori.CONNETTORE_CONNECTION_TIMEOUT));
connectionTimeoutConfigurazioneGlobale = this.properties.containsKey(CostantiConnettori.CONNETTORE_CONNECTION_TIMEOUT_GLOBALE);
}catch(Exception e){
this.logger.error("Parametro '"+CostantiConnettori.CONNETTORE_CONNECTION_TIMEOUT+"' errato",e);
}
}
if(connectionTimeout==-1){
connectionTimeout = HttpUtilities.HTTP_CONNECTION_TIMEOUT;
}
if(this.properties.get(CostantiConnettori.CONNETTORE_READ_CONNECTION_TIMEOUT)!=null){
try{
readConnectionTimeout = Integer.parseInt(this.properties.get(CostantiConnettori.CONNETTORE_READ_CONNECTION_TIMEOUT));
readConnectionTimeoutConfigurazioneGlobale = this.properties.containsKey(CostantiConnettori.CONNETTORE_READ_CONNECTION_TIMEOUT_GLOBALE);
}catch(Exception e){
this.logger.error("Parametro "+CostantiConnettori.CONNETTORE_READ_CONNECTION_TIMEOUT+" errato",e);
}
}
if(readConnectionTimeout==-1){
readConnectionTimeout = HttpUtilities.HTTP_READ_CONNECTION_TIMEOUT;
}
if(this.debug)
this.logger.info("Impostazione http timeout CT["+connectionTimeout+"] RT["+readConnectionTimeout+"]",false);
this.httpConn.setConnectTimeout(connectionTimeout);
this.httpConn.setReadTimeout(readConnectionTimeout);
// Authentication BASIC
if(this.debug)
this.logger.debug("Impostazione autenticazione...");
String user = null;
String password = null;
if(this.credenziali!=null){
user = this.credenziali.getUser();
password = this.credenziali.getPassword();
}else{
user = this.properties.get(CostantiConnettori.CONNETTORE_USERNAME);
password = this.properties.get(CostantiConnettori.CONNETTORE_PASSWORD);
}
if(user!=null && password!=null){
String authentication = user + ":" + password;
authentication = HttpConstants.AUTHORIZATION_PREFIX_BASIC + Base64Utilities.encodeAsString(authentication.getBytes());
setRequestHeader(HttpConstants.AUTHORIZATION,authentication, propertiesTrasportoDebug);
if(this.debug)
this.logger.info("Impostazione autenticazione (username:"+user+" password:"+password+") ["+authentication+"]",false);
}
// Authentication Token
NameValue nv = this.getTokenHeader();
if(nv!=null) {
if(this.requestMsg!=null && this.requestMsg.getTransportRequestContext()!=null) {
this.requestMsg.getTransportRequestContext().removeHeader(nv.getName()); // Fix: senno sovrascriveva il vecchio token
}
setRequestHeader(nv.getName(),nv.getValue(), propertiesTrasportoDebug);
if(this.debug)
this.logger.info("Impostazione autenticazione token (header-name '"+nv.getName()+"' value '"+nv.getValue()+"')",false);
}
// Authentication Api Key
String apiKey = this.properties.get(CostantiConnettori.CONNETTORE_APIKEY);
if(apiKey!=null && StringUtils.isNotEmpty(apiKey)){
String apiKeyHeader = this.properties.get(CostantiConnettori.CONNETTORE_APIKEY_HEADER);
if(apiKeyHeader==null || StringUtils.isEmpty(apiKeyHeader)) {
apiKeyHeader = CostantiConnettori.DEFAULT_HEADER_API_KEY;
}
setRequestHeader(apiKeyHeader,apiKey, propertiesTrasportoDebug);
if(this.debug)
this.logger.info("Impostazione autenticazione api key ["+apiKeyHeader+"]=["+apiKey+"]",false);
String appId = this.properties.get(CostantiConnettori.CONNETTORE_APIKEY_APPID);
if(appId!=null && StringUtils.isNotEmpty(appId)){
String appIdHeader = this.properties.get(CostantiConnettori.CONNETTORE_APIKEY_APPID_HEADER);
if(appIdHeader==null || StringUtils.isEmpty(appIdHeader)) {
appIdHeader = CostantiConnettori.DEFAULT_HEADER_APP_ID;
}
setRequestHeader(appIdHeader,appId, propertiesTrasportoDebug);
if(this.debug)
this.logger.info("Impostazione autenticazione api key (app id) ["+appIdHeader+"]=["+appId+"]",false);
}
}
// ForwardProxy
if(this.forwardProxy_headerName!=null && this.forwardProxy_headerValue!=null) {
if(this.requestMsg!=null && this.requestMsg.getTransportRequestContext()!=null) {
this.requestMsg.getTransportRequestContext().removeHeader(this.forwardProxy_headerName); // Fix: senno sovrascriveva il vecchio token
}
setRequestHeader(this.forwardProxy_headerName,this.forwardProxy_headerValue, propertiesTrasportoDebug);
if(this.debug)
this.logger.info("Impostazione ForwardProxy (header-name '"+this.forwardProxy_headerName+"' value '"+this.forwardProxy_headerValue+"')",false);
}
// Impostazione Proprieta del trasporto
if(this.debug)
this.logger.debug("Impostazione header di trasporto...");
this.forwardHttpRequestHeader();
if(this.propertiesTrasporto != null){
Iterator<String> keys = this.propertiesTrasporto.keySet().iterator();
while (keys.hasNext()) {
String key = (String) keys.next();
List<String> values = this.propertiesTrasporto.get(key);
if(this.debug) {
if(values!=null && !values.isEmpty()) {
for (String value : values) {
this.logger.info("Set Transport Header ["+key+"]=["+value+"]",false);
}
}
}
if(this.encodingRFC2047){
List<String> valuesEncoded = new ArrayList<>();
if(values!=null && !values.isEmpty()) {
for (String value : values) {
if(RFC2047Utilities.isAllCharactersInCharset(value, this.charsetRFC2047)==false){
String encoded = RFC2047Utilities.encode(new String(value), this.charsetRFC2047, this.encodingAlgorithmRFC2047);
//System.out.println("@@@@ CODIFICA ["+value+"] in ["+encoded+"]");
if(this.debug)
this.logger.info("RFC2047 Encoded value in ["+encoded+"] (charset:"+this.charsetRFC2047+" encoding-algorithm:"+this.encodingAlgorithmRFC2047+")",false);
valuesEncoded.add(encoded);
}
else{
valuesEncoded.add(value);
}
}
}
setRequestHeader(this.validazioneHeaderRFC2047, key, valuesEncoded, this.logger, propertiesTrasportoDebug);
}
else{
setRequestHeader(this.validazioneHeaderRFC2047, key, values, this.logger, propertiesTrasportoDebug);
}
}
}
// Aggiunga del SoapAction Header in caso di richiesta SOAP
// spostato sotto il forwardHeader per consentire alle trasformazioni di modificarla
if(this.isSoap && this.sbustamentoSoap == false){
if(this.debug)
this.logger.debug("Impostazione soap action...");
boolean existsTransportProperties = false;
if(TransportUtils.containsKey(this.propertiesTrasporto, Costanti.SOAP11_MANDATORY_HEADER_HTTP_SOAP_ACTION)){
this.soapAction = TransportUtils.getFirstValue(this.propertiesTrasporto, Costanti.SOAP11_MANDATORY_HEADER_HTTP_SOAP_ACTION);
existsTransportProperties = (this.soapAction!=null);
}
if(!existsTransportProperties) {
this.soapAction = soapMessageRequest.getSoapAction();
}
if(this.soapAction==null){
this.soapAction="\"OpenSPCoop\"";
}
if(MessageType.SOAP_11.equals(this.requestMsg.getMessageType()) && !existsTransportProperties){
// NOTA non quotare la soap action, per mantenere la trasparenza della PdD
setRequestHeader(Costanti.SOAP11_MANDATORY_HEADER_HTTP_SOAP_ACTION,this.soapAction, propertiesTrasportoDebug);
}
if(this.debug)
this.logger.info("SOAP Action inviata ["+this.soapAction+"]",false);
}
// Impostazione Metodo
if(this.debug)
this.logger.info("Impostazione "+this.httpMethod+"...",false);
HttpUtilities.setStream(this.httpConn, this.httpMethod, contentTypeRichiesta);
HttpBodyParameters httpBody = new HttpBodyParameters(this.httpMethod, contentTypeRichiesta);
// this.httpConn.setRequestMethod( method );
// this.httpConn.setDoOutput(false);
// this.httpConn.setDoInput(true);
// Spedizione byte
boolean httpBody_isDoOutput = httpBody.isDoOutput();
if(!httpBody.isDoOutput() && request.isForceSendContent()) {
this.httpConn.setDoOutput(true);
httpBody_isDoOutput = true;
}
if(httpBody_isDoOutput){
boolean consumeRequestMessage = true;
if(this.followRedirects){
consumeRequestMessage = false;
}
if(this.debug)
this.logger.debug("Spedizione byte (consume-request-message:"+consumeRequestMessage+")...");
OutputStream out = this.httpConn.getOutputStream();
if(this.isDumpBinarioRichiesta()) {
DumpByteArrayOutputStream bout = new DumpByteArrayOutputStream(this.dumpBinario_soglia, this.dumpBinario_repositoryFile, this.idTransazione,
TipoMessaggio.RICHIESTA_USCITA_DUMP_BINARIO.getValue());
try {
this.emitDiagnosticStartDumpBinarioRichiestaUscita();
if(this.isSoap && this.sbustamentoSoap){
this.logger.debug("Sbustamento...");
TunnelSoapUtils.sbustamentoMessaggio(soapMessageRequest,bout);
}else{
this.requestMsg.writeTo(bout, consumeRequestMessage);
}
bout.flush();
bout.close();
if(bout.isSerializedOnFileSystem()) {
try(FileInputStream fin = new FileInputStream(bout.getSerializedFile())) {
Utilities.copy(fin, out);
}
}
else {
out.write(bout.toByteArray());
}
out.flush();
out.close();
this.dataRichiestaInoltrata = DateManager.getDate();
this.dumpBinarioRichiestaUscita(bout, requestMessageType, contentTypeRichiesta, this.location, propertiesTrasportoDebug);
}finally {
try {
bout.clearResources();
}catch(Throwable t) {
this.logger.error("Release resources failed: "+t.getMessage(),t);
}
}
}else{
if(this.isSoap && this.sbustamentoSoap){
if(this.debug)
this.logger.debug("Sbustamento...");
TunnelSoapUtils.sbustamentoMessaggio(soapMessageRequest,out);
}else{
this.requestMsg.writeTo(out, consumeRequestMessage);
}
out.flush();
out.close();
this.dataRichiestaInoltrata = DateManager.getDate();
}
}
else {
if(this.isDumpBinarioRichiesta()) {
// devo registrare almeno gli header HTTP
this.emitDiagnosticStartDumpBinarioRichiestaUscita();
this.dumpBinarioRichiestaUscita(null, null, null, this.location, propertiesTrasportoDebug);
}
}
// Analisi MimeType e ContentLocation della risposta
if(this.debug)
this.logger.debug("Analisi risposta...");
Map<String, List<String>> mapHeaderHttpResponse = this.httpConn.getHeaderFields();
boolean protocolHttp10 = false;
if(mapHeaderHttpResponse!=null && mapHeaderHttpResponse.size()>0){
if(this.propertiesTrasportoRisposta==null){
this.propertiesTrasportoRisposta = new HashMap<>();
}
Iterator<String> itHttpResponse = mapHeaderHttpResponse.keySet().iterator();
while(itHttpResponse.hasNext()){
String keyHttpResponse = itHttpResponse.next();
List<String> valueHttpResponse = mapHeaderHttpResponse.get(keyHttpResponse);
if(valueHttpResponse!=null) {
for(int i=0;i<valueHttpResponse.size();i++){
if(this.debug)
this.logger.debug("HTTP risposta ["+keyHttpResponse+"] ["+valueHttpResponse.get(i)+"]...");
}
}
if(keyHttpResponse==null){ // Check per evitare la coppia che ha come chiave null e come valore HTTP OK 200
keyHttpResponse=HttpConstants.RETURN_CODE;
// es. HTTP/1.1 200 OK
if(valueHttpResponse!=null && valueHttpResponse.contains("/1.0")) {
protocolHttp10 = true;
}
}
this.propertiesTrasportoRisposta.put(keyHttpResponse, valueHttpResponse);
}
}
// KeepAlive
this.setKeepAlive(protocolHttp10, TransportUtils.getObjectAsString(mapHeaderHttpResponse, HttpConstants.CONNECTION));
// TipoRisposta
this.tipoRisposta = TransportUtils.getObjectAsString(mapHeaderHttpResponse, HttpConstants.CONTENT_TYPE);
// ContentLength della risposta
String contentLenghtString = TransportUtils.getObjectAsString(mapHeaderHttpResponse, HttpConstants.CONTENT_LENGTH);
if(contentLenghtString!=null){
this.contentLength = Integer.valueOf(contentLenghtString);
}
else{
if(this.httpConn.getContentLength()>0){
this.contentLength = this.httpConn.getContentLength();
}
}
// Parametri di imbustamento
if(this.isSoap){
this.imbustamentoConAttachment = false;
if("true".equals(TransportUtils.getObjectAsString(mapHeaderHttpResponse, this.openspcoopProperties.getTunnelSOAPKeyWord_headerTrasporto()))){
this.imbustamentoConAttachment = true;
}
this.mimeTypeAttachment = TransportUtils.getObjectAsString(mapHeaderHttpResponse, this.openspcoopProperties.getTunnelSOAPKeyWordMimeType_headerTrasporto());
if(this.mimeTypeAttachment==null)
this.mimeTypeAttachment = HttpConstants.CONTENT_TYPE_OPENSPCOOP2_TUNNEL_SOAP;
//System.out.println("IMB["+imbustamentoConAttachment+"] MIME["+mimeTypeAttachment+"]");
}
// Ricezione Risposta
if(this.debug)
this.logger.debug("Analisi risposta input stream e risultato http...");
this.initConfigurationAcceptOnlyReturnCode_202_200();
// return code
this.codice = this.httpConn.getResponseCode();
this.resultHTTPMessage = this.httpConn.getResponseMessage();
if(this.codice>=400){
this.isResponse = this.httpConn.getErrorStream();
}
else{
if(this.codice>299){
String redirectLocation = TransportUtils.getObjectAsString(mapHeaderHttpResponse, HttpConstants.REDIRECT_LOCATION);
// 3XX
if(this.followRedirects){
if(redirectLocation==null){
throw new Exception("Non è stato rilevato l'header HTTP ["+HttpConstants.REDIRECT_LOCATION+"] necessario alla gestione del Redirect (code:"+this.codice+")");
}
TransportUtils.removeObject(request.getConnectorProperties(), CostantiConnettori.CONNETTORE_LOCATION);
TransportUtils.removeObject(request.getConnectorProperties(), CostantiConnettori.CONNETTORE_HTTP_REDIRECT_NUMBER);
TransportUtils.removeObject(request.getConnectorProperties(), CostantiConnettori.CONNETTORE_HTTP_REDIRECT_ROUTE);
request.getConnectorProperties().put(CostantiConnettori.CONNETTORE_LOCATION, redirectLocation);
request.getConnectorProperties().put(CostantiConnettori.CONNETTORE_HTTP_REDIRECT_NUMBER, (this.numberRedirect+1)+"" );
if(this.routeRedirect!=null){
request.getConnectorProperties().put(CostantiConnettori.CONNETTORE_HTTP_REDIRECT_ROUTE, this.routeRedirect+" -> "+redirectLocation );
}else{
request.getConnectorProperties().put(CostantiConnettori.CONNETTORE_HTTP_REDIRECT_ROUTE, redirectLocation );
}
if(this.originalAbsolutePrefixForRelativeRedirectLocation==null) {
this.originalAbsolutePrefixForRelativeRedirectLocation = url.getProtocol()+"://"+url.getHost()+":"+url.getPort();
}
this.redirectLocation = redirectLocation; // per la prossima build()
if(this.redirectLocation.startsWith("/")) {
// relative
this.redirectLocation = this.originalAbsolutePrefixForRelativeRedirectLocation + this.redirectLocation;
}
this.logger.warn("(hope:"+(this.numberRedirect+1)+") Redirect verso ["+redirectLocation+"] ...");
if(this.numberRedirect==this.maxNumberRedirects){
throw new Exception("Gestione redirect (code:"+this.codice+" "+HttpConstants.REDIRECT_LOCATION+":"+redirectLocation+") non consentita ulteriormente, sono già stati gestiti "+this.maxNumberRedirects+" redirects: "+this.routeRedirect);
}
boolean acceptOnlyReturnCode_307 = false;
if(this.isSoap) {
if(ConsegnaContenutiApplicativi.ID_MODULO.equals(this.idModulo)){
acceptOnlyReturnCode_307 = this.openspcoopProperties.isAcceptOnlyReturnCode_307_consegnaContenutiApplicativi();
}
else{
// InoltroBuste e InoltroRisposte
acceptOnlyReturnCode_307 = this.openspcoopProperties.isAcceptOnlyReturnCode_307_inoltroBuste();
}
}
if(acceptOnlyReturnCode_307){
if(this.codice!=307){
throw new Exception("Return code ["+this.codice+"] (redirect "+HttpConstants.REDIRECT_LOCATION+":"+redirectLocation+") non consentito dal WS-I Basic Profile (http://www.ws-i.org/Profiles/BasicProfile-1.1-2004-08-24.html#HTTP_Redirect_Status_Codes)");
}
}
// Annullo precedente immagine
this.clearRequestHeader();
if(this.propertiesTrasportoRisposta!=null) {
this.propertiesTrasportoRisposta.clear();
}
this.contentLength = -1;
try {
return this.send(request); // caching ricorsivo non serve
}finally {
/*System.out.println("CHECK ["+redirectLocation+"]");
if(this.responseMsg!=null) {
System.out.println("MSG ["+this.responseMsg.getContentType()+"]");
this.responseMsg.writeTo(System.out, false);
}*/
}
}else{
if(this.isSoap) {
throw new Exception("Gestione redirect (code:"+this.codice+" "+HttpConstants.REDIRECT_LOCATION+":"+redirectLocation+") non attiva");
}
else {
this.logger.debug("Gestione redirect (code:"+this.codice+" "+HttpConstants.REDIRECT_LOCATION+":"+redirectLocation+") non attiva");
if(this.location!=null && redirectLocation!=null){
this.location = this.location+" [redirect-location: "+redirectLocation+"]";
}
if(httpBody.isDoInput()){
this.isResponse = this.httpConn.getInputStream();
if(this.isResponse==null) {
this.isResponse = this.httpConn.getErrorStream();
}
}
}
}
}
else{
if(this.isSoap && this.acceptOnlyReturnCode_202_200){
if(this.codice!=200 && this.codice!=202){
throw new Exception("Return code ["+this.codice+"] non consentito dal WS-I Basic Profile (http://www.ws-i.org/Profiles/BasicProfile-1.1-2004-08-24.html#HTTP_Success_Status_Codes)");
}
}
if(httpBody.isDoInput()){
this.isResponse = this.httpConn.getInputStream();
}
}
}
/* ------------ PostOutRequestHandler ------------- */
this.postOutRequest();
/* ------------ PreInResponseHandler ------------- */
this.preInResponse();
// Lettura risposta parametri NotifierInputStream per la risposta
this.notifierInputStreamParams = null;
if(this.preInResponseContext!=null){
this.notifierInputStreamParams = this.preInResponseContext.getNotifierInputStreamParams();
}
/* ------------ Gestione Risposta ------------- */
this.normalizeInputStreamResponse(readConnectionTimeout, readConnectionTimeoutConfigurazioneGlobale);
this.initCheckContentTypeConfiguration();
if(this.isDumpBinarioRisposta()){
if(!this.dumpResponse(this.propertiesTrasportoRisposta)) {
return false;
}
}
if(this.isRest){
if(this.doRestResponse()==false){
return false;
}
}
else{
if(this.doSoapResponse()==false){
return false;
}
}
if(this.debug)
this.logger.info("Gestione invio/risposta http effettuata con successo",false);
return true;
} catch(Exception e){
this.eccezioneProcessamento = e;
String msgErrore = this.readExceptionMessageFromException(e);
if(this.generateErrorWithConnectorPrefix) {
this.errore = "Errore avvenuto durante la consegna HTTP: "+msgErrore;
}
else {
this.errore = msgErrore;
}
this.logger.error("Errore avvenuto durante la consegna HTTP: "+msgErrore,e);
this.processConnectionTimeoutException(connectionTimeout, connectionTimeoutConfigurazioneGlobale, e, msgErrore);
this.processReadTimeoutException(readConnectionTimeout, readConnectionTimeoutConfigurazioneGlobale, e, msgErrore);
return false;
}
}
/**
* Effettua la disconnessione
*/
@Override
public void disconnect() throws ConnettoreException{
List<Throwable> listExceptionChiusura = new ArrayList<Throwable>();
try{
// Gestione finale della connessione
if(this.isResponse!=null){
if(this.debug && this.logger!=null)
this.logger.debug("Chiusura socket...");
try {
this.isResponse.close();
}
catch(Throwable t) {
if(this.logger!=null) {
this.logger.debug("Chiusura socket fallita: "+t.getMessage(),t);
}
listExceptionChiusura.add(t);
}
}
// fine HTTP.
if(this.httpConn!=null){
if(this.debug && this.logger!=null)
this.logger.debug("Chiusura connessione...");
try {
// FIX: https://docs.oracle.com/javase/8/docs/technotes/guides/net/http-keepalive.html
/*
* Do not abandon a connection by ignoring the response body. Doing so may results in idle TCP connections. That needs to be garbage collected when they are no longer referenced.
* If getInputStream() successfully returns, read the entire response body.
* When calling getInputStream() from HttpURLConnection, if an IOException occurs, catch the exception and call getErrorStream() to get the response body (if there is any).
* Reading the response body cleans up the connection even if you are not interested in the response content itself. But if the response body is long and you are not interested in the rest of it after seeing the beginning, you can close the InputStream.
* But you need to be aware that more data could be on its way. Thus the connection may not be cleared for reuse.
*
* (Quindi se keepAlive == true)
* If client called HttpURLConnection.getInputSteam().close(),
* the later call to HttpURLConnection.disconnect() will NOT close the Socket. i.e. The Socket is reused (cached)
* If client does not call close(), call disconnect() will close the InputStream and close the Socket.
*
* !!! So in order to reuse the Socket, just call InputStream.close(). Do not call HttpURLConnection.disconnect().
*
* */
if(!this.keepAlive) {
this.httpConn.disconnect();
}
}catch(Throwable t) {
if(this.logger!=null) {
this.logger.debug("Chiusura connessione fallita: "+t.getMessage(),t);
}
listExceptionChiusura.add(t);
}
}
// super.disconnect (Per risorse base)
try {
super.disconnect();
}catch(Throwable t) {
if(this.logger!=null) {
this.logger.debug("Chiusura risorse fallita: "+t.getMessage(),t);
}
listExceptionChiusura.add(t);
}
}catch(Exception e){
throw new ConnettoreException("Chiusura connessione non riuscita: "+e.getMessage(),e);
}
if(listExceptionChiusura!=null && !listExceptionChiusura.isEmpty()) {
org.openspcoop2.utils.UtilsMultiException multiException = new org.openspcoop2.utils.UtilsMultiException(listExceptionChiusura.toArray(new Throwable[1]));
throw new ConnettoreException("Chiusura connessione non riuscita: "+multiException.getMessage(),multiException);
}
}
private void setKeepAlive(boolean protocolHttp10, String connectionHeaderValue) {
/* https://tools.ietf.org/html/rfc7230#section-6.3 */
/*
A recipient determines whether a connection is persistent or not
based on the most recently received message's protocol version and
Connection header field (if any):
- If the "close" connection option is present, the connection will
not persist after the current response; else,
- If the received protocol is HTTP/1.1 (or later), the connection
will persist after the current response; else,
- If the received protocol is HTTP/1.0, the "keep-alive" connection
option is present, the recipient is not a proxy, and the recipient
wishes to honor the HTTP/1.0 "keep-alive" mechanism, the
connection will persist after the current response; otherwise,
- The connection will close after the current response.
*/
if(HttpConstants.CONNECTION_VALUE_CLOSE.equalsIgnoreCase(connectionHeaderValue)) {
this.keepAlive = false;
}
else if(!protocolHttp10) {
this.keepAlive = true;
}
else if(connectionHeaderValue!=null){
this.keepAlive = HttpConstants.CONNECTION_VALUE_KEEP_ALIVE.equalsIgnoreCase(connectionHeaderValue);
}
else {
this.keepAlive = false;
}
}
/**
* Ritorna l'informazione su dove il connettore sta spedendo il messaggio
*
* @return location di inoltro del messaggio
*/
@Override
public String getLocation(){
if(this.location==null){
// può darsi che per un errore non sia ancora stata inizializzata la location
try{
this.buildLocation();
}catch(Throwable t){}
}
if(this.location!=null){
String l = new String(this.location);
if(this.routeRedirect!=null){
l = l+" [redirects route path: "+this.routeRedirect+"]";
}
if(this.proxyType!=null){
l = l+" [proxy: "+this.proxyHostname+":"+this.proxyPort+"]";
}
// if(this.forwardProxy!=null && this.forwardProxy.isEnabled()) {
// l = l+" [govway-proxy]";
// }
return l;
}
return null;
}
private void buildLocation() throws ConnettoreException {
if(this.redirectLocation!=null) {
this.location = this.redirectLocation;
}
else {
this.location = TransportUtils.getObjectAsString(this.properties,CostantiConnettori.CONNETTORE_LOCATION);
}
NameValue nv = this.getTokenQueryParameter();
if(nv!=null) {
if(this.requestMsg!=null && this.requestMsg.getTransportRequestContext()!=null) {
this.requestMsg.getTransportRequestContext().removeParameter(nv.getName()); // Fix: senno sovrascriveva il vecchio token
}
if(this.propertiesUrlBased==null) {
this.propertiesUrlBased = new HashMap<>();
}
TransportUtils.setParameter(this.propertiesUrlBased, nv.getName(), nv.getValue());
}
if(this.redirectLocation==null) {
this.location = ConnettoreUtils.buildLocationWithURLBasedParameter(this.logger!=null ? this.logger.getLogger() : null, this.requestMsg,
this.connettoreHttps ? TipiConnettore.HTTPS.toString() : TipiConnettore.HTTP.toString(),
this.propertiesUrlBased, this.location,
this.getProtocolFactory(), this.idModulo);
}
this.updateLocation_forwardProxy(this.location);
}
private void setRequestHeader(boolean validazioneHeaderRFC2047, String key, List<String> values, ConnettoreLogger logger, Map<String, List<String>> propertiesTrasportoDebug) throws Exception {
if(validazioneHeaderRFC2047){
try{
RFC2047Utilities.validHeader(key, values);
setRequestHeader(key, values, propertiesTrasportoDebug);
}catch(UtilsException e){
logger.error(e.getMessage(),e);
}
}
else{
setRequestHeader(key,values, propertiesTrasportoDebug);
}
}
@Override
protected void setRequestHeader(String key,List<String> values) throws Exception {
if(values!=null && !values.isEmpty()) {
for (String value : values) {
this.httpConn.addRequestProperty(key,value);
}
}
}
}
class HttpAuthenticator extends Authenticator{
private String username;
private String password;
HttpAuthenticator(String u,String p){
super();
this.username = u;
this.password = p;
}
@Override
public PasswordAuthentication getPasswordAuthentication(){
return new PasswordAuthentication(this.username, this.password.toCharArray());
}
}