package com.doumee.core.utils; import lombok.extern.slf4j.Slf4j; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.StringEntity; import org.apache.http.impl.auth.DigestScheme; import org.apache.http.impl.client.BasicAuthCache; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.io.InputStream; import java.net.URI; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.security.cert.X509Certificate; /** * 海康 ISAPI HTTP 客户端(Digest 认证,支持 HTTP/HTTPS) */ @Slf4j public class IsapiHttpUtil { private static final int CONNECT_TIMEOUT = 10000; private static final int SOCKET_TIMEOUT = 30000; private static final int DOWNLOAD_SOCKET_TIMEOUT = 300000; private IsapiHttpUtil() { } public static String doGet(String host, int port, String username, String password, String uri) { return doGet(host, port, false, username, password, uri); } public static String doGet(String host, int port, boolean https, String username, String password, String uri) { String url = buildUrl(host, port, https, uri); HttpGet request = new HttpGet(url); return executeString(request, host, port, https, username, password, SOCKET_TIMEOUT); } public static String doPost(String host, int port, String username, String password, String uri, String body, String contentType) { return doPost(host, port, false, username, password, uri, body, contentType); } public static String doPost(String host, int port, boolean https, String username, String password, String uri, String body, String contentType) { String url = buildUrl(host, port, https, uri); HttpPost request = new HttpPost(url); if (body != null) { request.setEntity(new StringEntity(body, StandardCharsets.UTF_8)); } if (contentType != null) { request.setHeader("Content-Type", contentType); } return executeString(request, host, port, https, username, password, SOCKET_TIMEOUT); } public static String doPut(String host, int port, boolean https, String username, String password, String uri, String body, String contentType) { String url = buildUrl(host, port, https, uri); HttpPut request = new HttpPut(url); if (body != null) { request.setEntity(new StringEntity(body, StandardCharsets.UTF_8)); } if (contentType != null) { request.setHeader("Content-Type", contentType); } return executeString(request, host, port, https, username, password, SOCKET_TIMEOUT); } /** * ISAPI 文件下载:GET /ISAPI/ContentMgmt/download?token=xxx,Body 含 playbackURI */ public static InputStream doDownload(String host, int port, String username, String password, String uri, String downloadBody) { return doDownload(host, port, false, username, password, uri, downloadBody); } public static InputStream doDownload(String host, int port, boolean https, String username, String password, String uri, String downloadBody) { String url = buildUrl(host, port, https, uri); HttpGetWithEntity request = new HttpGetWithEntity(url); if (downloadBody != null) { request.setEntity(new StringEntity(downloadBody, StandardCharsets.UTF_8)); request.setHeader("Content-Type", "application/xml"); } return executeStream(request, host, port, https, username, password, DOWNLOAD_SOCKET_TIMEOUT); } /** 海康 ISAPI 下载接口使用 GET + Body */ static class HttpGetWithEntity extends HttpEntityEnclosingRequestBase { HttpGetWithEntity(String uri) { setURI(URI.create(uri)); } @Override public String getMethod() { return "GET"; } } private static String buildUrl(String host, int port, boolean https, String uri) { String scheme = https ? "https" : "http"; if (!uri.startsWith("/")) { uri = "/" + uri; } return scheme + "://" + host + ":" + port + uri; } private static String executeString(HttpRequestBase request, String host, int port, boolean https, String username, String password, int socketTimeout) { try (CloseableHttpClient httpClient = buildClient(https, buildCredentials(host, port, https, username, password)); CloseableHttpResponse response = execute(httpClient, request, host, port, https, username, password, socketTimeout)) { HttpEntity entity = response.getEntity(); if (entity == null) { return null; } return EntityUtils.toString(entity, StandardCharsets.UTF_8); } catch (Exception e) { log.error("ISAPI请求失败 {}: {}", request.getURI(), e.getMessage(), e); return null; } } private static InputStream executeStream(HttpRequestBase request, String host, int port, boolean https, String username, String password, int socketTimeout) { try { CloseableHttpClient httpClient = buildClient(https, buildCredentials(host, port, https, username, password)); CloseableHttpResponse response = execute(httpClient, request, host, port, https, username, password, socketTimeout); int status = response.getStatusLine().getStatusCode(); if (status < 200 || status >= 300) { log.warn("ISAPI下载HTTP失败 {} status={}", request.getURI(), status); EntityUtils.consumeQuietly(response.getEntity()); response.close(); httpClient.close(); return null; } HttpEntity entity = response.getEntity(); if (entity == null) { response.close(); httpClient.close(); return null; } return entity.getContent(); } catch (Exception e) { log.error("ISAPI下载失败 {}: {}", request.getURI(), e.getMessage(), e); return null; } } private static CredentialsProvider buildCredentials(String host, int port, boolean https, String username, String password) { CredentialsProvider credsProvider = new BasicCredentialsProvider(); String scheme = https ? "https" : "http"; credsProvider.setCredentials(new AuthScope(host, port, AuthScope.ANY_REALM, scheme), new UsernamePasswordCredentials(username, password)); credsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password)); return credsProvider; } private static CloseableHttpResponse execute(CloseableHttpClient httpClient, HttpRequestBase request, String host, int port, boolean https, String username, String password, int socketTimeout) throws Exception { RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout(CONNECT_TIMEOUT) .setSocketTimeout(socketTimeout) .setConnectionRequestTimeout(CONNECT_TIMEOUT) .build(); request.setConfig(requestConfig); CredentialsProvider credsProvider = buildCredentials(host, port, https, username, password); HttpHost httpHost = new HttpHost(host, port, https ? "https" : "http"); HttpClientContext context = HttpClientContext.create(); context.setCredentialsProvider(credsProvider); log.info("ISAPI请求: {}://{}:{}{}", https ? "https" : "http", host, port, request.getURI().getRawPath()); CloseableHttpResponse response = httpClient.execute(httpHost, request, context); int status = response.getStatusLine().getStatusCode(); if (status != 401) { return response; } Header authHeader = findDigestAuthHeader(response); if (authHeader == null) { log.warn("ISAPI 401 但未返回 Digest 挑战头: {} status={}", request.getURI(), status); return response; } EntityUtils.consumeQuietly(response.getEntity()); response.close(); DigestScheme digestScheme = new DigestScheme(); digestScheme.processChallenge(authHeader); BasicAuthCache authCache = new BasicAuthCache(); authCache.put(httpHost, digestScheme); HttpClientContext retryContext = HttpClientContext.create(); retryContext.setCredentialsProvider(credsProvider); retryContext.setAuthCache(authCache); CloseableHttpResponse retryResponse = httpClient.execute(httpHost, request, retryContext); log.info("ISAPI Digest 重试: {} status={}", request.getURI(), retryResponse.getStatusLine().getStatusCode()); return retryResponse; } private static Header findDigestAuthHeader(CloseableHttpResponse response) { Header[] headers = response.getHeaders("WWW-Authenticate"); if (headers == null || headers.length == 0) { return null; } for (Header header : headers) { if (header.getValue() != null && header.getValue().toLowerCase().contains("digest")) { return header; } } return headers[0]; } private static CloseableHttpClient buildClient(boolean https, CredentialsProvider credsProvider) throws Exception { HttpClientBuilder builder = HttpClients.custom().setDefaultCredentialsProvider(credsProvider); if (!https) { return builder.build(); } TrustManager[] trustManagers = new TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) { } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } } }; SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, trustManagers, new SecureRandom()); SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); return builder.setSSLSocketFactory(sslFactory).build(); } }