Classes utilitaire qui fourni des méthodes pour envoyer et recevoir des messages depuis des serveurs distants en utilisant le protocole HTTP. Cette classe ne contient pas les methodes "static" et elle doit être instanciée avant utilisation, ce qui permet la création d'une classe simulée (mock), l'étendant, spécifiquement pour des besoins de tests unitaires et d'intégration sans dépendance sur une réseau réelle.

This commit is contained in:
2025-10-24 17:00:12 -07:00
parent 2269b610bc
commit 2f0a5f652b

View File

@@ -0,0 +1,202 @@
package common;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Utility methods to support HTTP network communication. Declared as a
* non-static class so that a mock implementation can be instantiated for testing.
*/
public class HttpUtils
{
Logger LOGGER = Logger.getLogger( HttpUtils.class.getName() );
/**
* Sends an HTTP request. If postData is not null the request will be a POsT
* request otherwise it will be a GET request.
*
* @param urlString The URL to send the request to.
* @param postData Optional POST data. if not null, http request will be POST, otherwise it will be
* a GET
* @param contentType Optional content type for POST data.
* @param responseHeaders A map to store response headers.
* @param outputFilePath If not null, and the file exists, save the response to this file.
* @param resume Only applicable when outputFilePath is present. If false the
* target file should be truncated, true to try resume download and append
* data (works only in combination with a valid outputStream)
* @return The response from the server as a string.
*/
public String HTTPSendRequest( String urlString, String postData, String contentType,
Map<String, String> responseHeaders, String outputFilePath, boolean resume )
{
try
{
URL url = new URL( urlString );
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setUseCaches( false );
connection.setDefaultUseCaches( false );
if (postData != null && !postData.isEmpty())
{
connection.setRequestMethod( "POST" );
connection.setDoOutput( true );
if (contentType != null && !contentType.isEmpty())
{
connection.setRequestProperty( "Content-Type", contentType );
}
// Copy POST data
try (OutputStream os = connection.getOutputStream())
{
byte[] input = postData.getBytes( StandardCharsets.UTF_8 );
os.write( input, 0, input.length );
}
}
else
{
connection.setRequestMethod( "GET" );
}
if (resume && outputFilePath != null && Files.exists( Paths.get( outputFilePath ) ))
{
long existingSize = Files.size( Paths.get( outputFilePath ) );
connection.setRequestProperty( "Range", "bytes=" + existingSize + "-" );
LOGGER.info( "Resuming download from byte: " + existingSize );
}
// Get response
int responseCode = connection.getResponseCode();
LOGGER.log( Level.FINER, "HTTP Response Code: " + responseCode );
// Populate response headers
if (responseHeaders != null)
{
connection.getHeaderFields().forEach( ( key, values ) ->
{
if (key != null && !values.isEmpty())
{
responseHeaders.put( key, values.get( 0 ) ); // Take first value
}
} );
}
if (responseCode >= 200 && responseCode < 300) // || responseCode == 206 /* Partial Content for resume */)
{
InputStream is = connection.getInputStream();
if (outputFilePath != null && !outputFilePath.isEmpty())
{
try (FileOutputStream fos = new FileOutputStream( outputFilePath, resume ))
{ // Append if resuming
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = is.read( buffer )) != -1)
{
fos.write( buffer, 0, bytesRead );
}
}
return ""; // Return empty string if writing to file
}
else
{
try (BufferedReader in = new BufferedReader( new InputStreamReader( is, StandardCharsets.UTF_8 ) ))
{
StringBuilder response = new StringBuilder();
String line;
while ((line = in.readLine()) != null)
{
response.append( line );
}
return response.toString();
}
}
}
else
{
try (BufferedReader in = new BufferedReader(
new InputStreamReader( connection.getErrorStream(), StandardCharsets.UTF_8 ) ))
{
StringBuilder errorResponse = new StringBuilder();
String line;
while ((line = in.readLine()) != null)
{
errorResponse.append( line );
}
// Attempt to parse error XML if available
if (errorResponse.length() > 0 && errorResponse.toString().startsWith( "<error" ))
{
Document errorDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
new ByteArrayInputStream(
errorResponse.toString().getBytes( StandardCharsets.UTF_8 ) ) );
String errorData = XmlUtils.extractTextAttribute( errorDoc.getDocumentElement(),
"/error", "data", false );
if (!errorData.isEmpty())
{
throw new GourouException( GourouException.GOUROU_ADEPT_ERROR,
"Server Error: " + errorData );
}
}
throw new GourouException(
"HTTP Request failed with code: " + responseCode + ", Response: " + errorResponse.toString() );
}
}
}
catch (IOException | ParserConfigurationException | SAXException e)
{
LOGGER.log( Level.SEVERE, "HTTP Request error: " + e.getMessage(), e );
throw new GourouException( "HTTP Request failed", e );
}
}
/**
* Makes an HTTP GET request
*
* @param urlString The URL to send the request to.
* @param contentType Optional content type for POST data.
* @param responseHeaders A map to store response headers.
* @return The response from the server as a string.
*/
public String HTTPGetRequest( String urlString, String contentType, Map<String, String> responseHeaders )
{
return HTTPSendRequest( urlString, null, contentType,
responseHeaders, null, false );
}
/**
* Sends an HTTP request. If postData is not null the request will be a POST
* request otherwise it will be a GET request. The response is assumed to be
* XML, and will be parsed into an XML Document.
*
* @param url The URL to send the request to.
* @param postData Optional POST data. if not null, http request will be POST, otherwise it will be a get
* @param contentType Optional content type for POST data.
* @param responseHeaders A map to store response headers.
* @param resume remains to be seen.
*
* @return The response from the server as an XML Document.
*/
public Document sendHTTPRequestForXML( String url, String postData, String contentType,
Map<String, String> responseHeaders, boolean resume )
throws IOException, SAXException, ParserConfigurationException
{
String response = HTTPSendRequest( url, postData, contentType, responseHeaders, null, resume );
byte[] responseData = response.getBytes( StandardCharsets.UTF_8 );
DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance();
fac.setNamespaceAware( true );
DocumentBuilder db = fac.newDocumentBuilder();
Document responseDoc = db.parse( new ByteArrayInputStream( responseData ) );
responseDoc.getDocumentElement().normalize();
return responseDoc;
}
}