UrlConnectionConnection.java

/*
 * GovWay - A customizable API Gateway 
 * https://govway.org
 * 
 * Copyright (c) 2005-2025 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.utils.transport.http;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;

import org.apache.commons.lang3.StringUtils;
import org.openspcoop2.utils.CopyStream;
import org.openspcoop2.utils.LoggerWrapperFactory;
import org.openspcoop2.utils.Utilities;
import org.openspcoop2.utils.UtilsException;
import org.openspcoop2.utils.io.Base64Utilities;

/**
 * UrlConnectionConnection
 *
 * @author Tommaso Burlon (tommaso.burlon@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */
class UrlConnectionConnection extends HttpLibraryConnection {

	@Override
	public HttpResponse send(HttpRequest request, SSLContext sslContext, OCSPTrustManager ocspTrustManager) throws UtilsException, IOException {
		
		
		if(request.getMethod()==null){
			throw new UtilsException("HttpMethod required");
		}
		
		ByteArrayOutputStream outResponse = null;
		InputStream is = null;
		
		try {
			URLConnection connection = null;
			URL url = URI.create(request.getUrl()).toURL();
			
			
			// istauro la connessione utilizzando un proxy se abilitato
			if(request.getProxyType()==null){
				if(request.isDebug()) {
					request.logInfo("Creazione connessione alla URL ["+request.getUrl()+"]...");
				}
				connection = url.openConnection();
			} else { 
				if(request.isDebug()) {
					request.logInfo("Creazione connessione alla URL ["+request.getUrl()+"] (via proxy "+
							request.getProxyHostname()+":"+request.getProxyPort()+") (username["+request.getProxyUsername()+"] password["+request.getProxyPassword()+"])...");
				}
				if(request.getProxyHostname()==null) {
					throw new UtilsException("Proxy require a hostname");
				}
				Proxy proxy = new Proxy(request.getProxyType(), new InetSocketAddress(request.getProxyHostname(), request.getProxyPort()));
				connection = url.openConnection(proxy);
				
				// Proxy Authentication BASIC
				if(request.getProxyUsername()!=null && request.getProxyPassword()!=null){
					String authentication = request.getProxyUsername() + ":" + request.getProxyPassword();
					authentication = HttpConstants.AUTHORIZATION_PREFIX_BASIC + Base64Utilities.encodeAsString(authentication.getBytes());
					connection.addRequestProperty(HttpConstants.PROXY_AUTHORIZATION,authentication);
				}
			}
			
			HttpURLConnection httpConn = (HttpURLConnection) connection;
			
			// configuro il contesto ssl se abilitato
			if(sslContext!=null) {
				
				HttpsURLConnection httpsConn = (HttpsURLConnection) httpConn;
				
				SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
				if(request.isDebug()) {
					String clientCertificateConfigurated = request.getKeyStorePath();
					sslSocketFactory = new WrappedLogSSLSocketFactory(sslSocketFactory, 
							request.getLog(), "",
							clientCertificateConfigurated);
				}		
				httpsConn.setSSLSocketFactory(sslSocketFactory);
				
				if(!request.isHostnameVerifier()) {
					if(request.isDebug()) {
						request.logInfo("HostName verifier disabilitato");
					}
					SSLHostNameVerifierDisabled disabilitato = new SSLHostNameVerifierDisabled(LoggerWrapperFactory.getLogger(HttpUtilities.class));
					httpsConn.setHostnameVerifier(disabilitato);
				}
				else if(request.isDebug()) {
					request.logInfo("HostName verifier abilitato");
				}
			}
			else {
				if(request.isDebug() && (httpConn instanceof HttpsURLConnection)) { // compatibile compilazione java 11
					HttpsURLConnection httpsConn = (HttpsURLConnection) httpConn;
					if(httpsConn.getSSLSocketFactory()!=null) {
						SSLSocketFactory sslSocketFactory = httpsConn.getSSLSocketFactory();
						String clientCertificateConfigurated = SSLUtilities.getJvmHttpsClientCertificateConfigurated();
						sslSocketFactory = new WrappedLogSSLSocketFactory(sslSocketFactory, 
								request.getLog(), "",
								clientCertificateConfigurated);
						httpsConn.setSSLSocketFactory(sslSocketFactory);
					}
				}
			}
			
			// imposto il contentType
			String contentType = request.getContentType();
			if(contentType!=null){
				if(request.isDebug()) {
	        		request.logInfo("Impostazione Content-Type ["+contentType+"]");
	        	}
				httpConn.setRequestProperty(HttpConstants.CONTENT_TYPE,contentType);
			}
			else if(request.getContent()!=null){
				String ct = request.getHeaderFirstValue(HttpConstants.CONTENT_TYPE);
				if(ct==null || StringUtils.isEmpty(ct)) {
					throw new UtilsException("Content require a Content Type");
				}
				else {
					contentType = ct; // negli header verra impostato sotto
				}
			}
			
			// imposto i timeouts
			if(request.isDebug())
				request.logInfo("Impostazione http timeout CT["+request.getConnectTimeout()+"] RT["+request.getReadTimeout()+"]");
			httpConn.setConnectTimeout(request.getConnectTimeout());
			httpConn.setReadTimeout(request.getReadTimeout());
			
			// imposto la gestione dei redirects
			if(request.getFollowRedirects()!=null) {
				if(request.isDebug()) {
	        		request.logInfo("Redirect strategy abilitato");
	        	}
				httpConn.setInstanceFollowRedirects(request.getFollowRedirects());
			}
			else {
				if(request.isDebug()) {
	        		request.logInfo("Redirect strategy disabilitato");
	        	}
				httpConn.setInstanceFollowRedirects(false);
			}
			
			// autenticazione basic
			if(request.getUsername() != null && request.getPassword() != null){
				String authentication = request.getUsername() + ":" + request.getPassword();
				authentication = HttpConstants.AUTHORIZATION_PREFIX_BASIC + Base64Utilities.encodeAsString(authentication.getBytes());
				if(request.isDebug())
					request.logInfo("Impostazione autenticazione (username:"+request.getUsername()+" password:"+request.getPassword()+") ["+authentication+"]");
				httpConn.setRequestProperty(HttpConstants.AUTHORIZATION,authentication);
			}
			
			// autenticazione bearer
			if(request.getBearerToken()!=null){
				String authorizationHeader = HttpConstants.AUTHORIZATION_PREFIX_BEARER+request.getBearerToken();
				if(request.isDebug())
					request.logInfo("Impostazione autenticazione bearer ["+authorizationHeader+"]");
				httpConn.setRequestProperty(HttpConstants.AUTHORIZATION,authorizationHeader);
			}
			
			// Authentication Api Key
			String apiKey = request.getApiKey();
			if(apiKey!=null && StringUtils.isNotEmpty(apiKey)){
				String apiKeyHeader = request.getApiKeyHeader();
				if(apiKeyHeader==null || StringUtils.isEmpty(apiKeyHeader)) {
					apiKeyHeader = HttpConstants.AUTHORIZATION_HEADER_API_KEY;
				}
				httpConn.setRequestProperty(apiKeyHeader,apiKey);
				if(request.isDebug())
					request.logInfo("Impostazione autenticazione api key ["+apiKeyHeader+"]=["+apiKey+"]");
				
				String appId = request.getAppId();
				if(appId!=null && StringUtils.isNotEmpty(appId)){
					String appIdHeader = request.getAppIdHeader();
					if(appIdHeader==null || StringUtils.isEmpty(appIdHeader)) {
						appIdHeader = HttpConstants.AUTHORIZATION_HEADER_APP_ID;
					}
					httpConn.setRequestProperty(appIdHeader,appId);
					if(request.isDebug())
						request.logInfo("Impostazione autenticazione api key (app id) ["+appIdHeader+"]=["+appId+"]");
				}
			}
			
			// headers
			Map<String, List<String>> requestHeaders = request.getHeadersValues();
			if(requestHeaders!=null && requestHeaders.size()>0){
				Iterator<String> itReq = requestHeaders.keySet().iterator();
				while (itReq.hasNext()) {
					String key = itReq.next();
					List<String> values = requestHeaders.get(key);
					if(values!=null && !values.isEmpty()) {
						for (String value : values) {
							if(request.isDebug()) {
				        		request.logInfo("Aggiungo header ["+key+"]=["+value+"]");
				        	}
							httpConn.addRequestProperty(key, value);		
						}
					}
				}
			}
			
			// Verifica solo della connessione
			if(request.isCheckConnection()) {
				try {
					if(request.isDebug())
						request.logDebug("Connessione in corso ...");
					httpConn.connect();
					if(request.isDebug())
						request.logDebug("Connessione effettuata con successo");
					return null;
				}finally {
					safeDisconnect(httpConn);
				}
			}
			
			// gestione throttling
			boolean sendThrottling = false;
			if(request.getThrottlingSendByte()!=null && request.getThrottlingSendByte()>0 && 
					request.getThrottlingSendMs()!=null && request.getThrottlingSendMs()>0) {
				sendThrottling = true;
			}
			
			if(sendThrottling || request.isForceTransferEncodingChunked()) {
				httpConn.setChunkedStreamingMode(0);
			}
			HttpUtilities.setStream(httpConn, request.getMethod(), contentType);

			HttpBodyParameters httpContent = new  HttpBodyParameters(request.getMethod(), contentType);
			// Spedizione byte
			if(httpContent.isDoOutput() && request.getContent() != null){
				OutputStream out = httpConn.getOutputStream();
				if(sendThrottling) {
					int lengthSendContent = request.getContent().length;
					int length = 0;
					for (int i = 0; i < lengthSendContent; i += length) {
						length = request.getThrottlingSendByte();
						int remaining = lengthSendContent-i;
						if(remaining<length) {
							length = remaining;
						}
						out.write(request.getContent(),i,length);
						out.flush();
						Utilities.sleep(request.getThrottlingSendMs());
					}
				}
				else {
					out.write(request.getContent());
				}
				out.flush();
				out.close();
			}
			else if(httpContent.isDoOutput() && request.getContentStream() != null){
				OutputStream out = httpConn.getOutputStream();
				CopyStream.copy(request.getContentStream(), out);
			}
			
			HttpResponse response = new HttpResponse();
			
			// Ricezione header
			Map<String, List<String>> mapHeaderHttpResponse = httpConn.getHeaderFields();
			if(mapHeaderHttpResponse!=null && mapHeaderHttpResponse.size()>0){
				Iterator<String> itHttpResponse = mapHeaderHttpResponse.keySet().iterator();
				while(itHttpResponse.hasNext()){
					String keyHttpResponse = itHttpResponse.next();
					List<String> valueHttpResponse = mapHeaderHttpResponse.get(keyHttpResponse);
					if(keyHttpResponse==null){ // Check per evitare la coppia che ha come chiave null e come valore HTTP OK 200
						keyHttpResponse=HttpConstants.RETURN_CODE;
					}
					response.addHeader(keyHttpResponse, valueHttpResponse);
				}
			}
			
			// ContentType Risposta
			if(response.getHeadersValues()!=null && !response.getHeadersValues().isEmpty()){
				response.setContentType(response.getHeaderFirstValue(HttpConstants.CONTENT_TYPE));
			}

			// Ricezione Result HTTP Code
			int resultHTTPOperation = httpConn.getResponseCode();

			response.setResultHTTPOperation(resultHTTPOperation);
			
			// Ricezione Risposta
			if(httpContent.isDoInput()){
				outResponse = new ByteArrayOutputStream();
				if(resultHTTPOperation>399){
					is = httpConn.getErrorStream();
					if(is==null){
						is = httpConn.getInputStream();
					}
				}else{
					is = httpConn.getInputStream();
					if(is==null){
						is = httpConn.getErrorStream();
					}
				}
				CopyStream.copy(is, outResponse);
				is.close();
				outResponse.flush();
				outResponse.close();
				response.setContent(outResponse.toByteArray());
			}
				
			// fine HTTP.
			httpConn.disconnect();
	
			// certificati server
			if(ocspTrustManager!=null) {
				response.setServerCertificate(ocspTrustManager.getPeerCertificates());
			}
			
			return response;
			
		} catch (IOException e) {
			throw e;
		} catch (Exception e) {
			throw new UtilsException(e);
		} finally {
			if (is != null) {
				is.close();
			}
			
			if (outResponse != null) {
				outResponse.close();
			}
			
		}
	}

	private void safeDisconnect(HttpURLConnection httpConn) {
		try {
			httpConn.disconnect();
		}catch(Exception ignore) {
			// ignore
		}
	}
}