java实现http与https协议

该博客介绍了HTTPS协议相对于HTTP协议的安全增强,如通过SSL加入加密传输和身份认证,确保数据安全。同时,代码展示了如何在Java中配置HTTPS连接,包括证书验证和信任管理。此外,还提及了推荐直接使用HTTP协议进行下载,因为HTTPS可能导致较高的CPU占用。
摘要由CSDN通过智能技术生成

http协议安全性差,单纯进行传输

https协议相比http加入了ssl,可进行加密传输、身份认证等保证数据安全的操作

https需要进行证书验证

package com.acgist.snail.net.http;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Map;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509TrustManager;

import com.acgist.snail.config.SystemConfig;
import com.acgist.snail.context.wrapper.HttpHeaderWrapper;
import com.acgist.snail.logger.Logger;
import com.acgist.snail.logger.LoggerFactory;
import com.acgist.snail.net.NetException;
import com.acgist.snail.utils.IoUtils;
import com.acgist.snail.utils.MapUtils;
import com.acgist.snail.utils.NumberUtils;

/**
 * <p>HTTP客户端</p>
 * <p>配置参考:https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html</p>
 * <p>推荐直接使用HTTP协议下载:HTTPS下载CPU占用较高</p>
 * 
 * @author acgist
 */
public final class HttpClient {

	private static final Logger LOGGER = LoggerFactory.getLogger(HttpClient.class);
	
	/**
	 * <p>请求方式</p>
	 * 
	 * @author acgist
	 */
	public enum Method {
		
		/**
		 * <p>GET请求</p>
		 */
		GET,
		/**
		 * <p>HEAD请求</p>
		 */
		HEAD,
		/**
		 * <p>POST请求</p>
		 */
		POST;
		
	}
	
	/**
	 * <p>HTTP客户端信息(User-Agent)</p>
	 */
	private static final String USER_AGENT;
	
	static {
		final StringBuilder userAgentBuilder = new StringBuilder();
		userAgentBuilder
			.append(SystemConfig.getNameEn())
			.append("/")
			.append(SystemConfig.getVersion())
			.append(" (+")
			.append(SystemConfig.getSupport())
			.append(")");
		USER_AGENT = userAgentBuilder.toString();
		LOGGER.debug("HTTP客户端信息(User-Agent):{}", USER_AGENT);
		// 配置HTTPS
		final SSLContext sslContext = buildSSLContext();
		if(sslContext != null) {
			HttpsURLConnection.setDefaultHostnameVerifier(SnailHostnameVerifier.INSTANCE);
			HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
		}
	}
	
	/**
	 * <p>请求地址</p>
	 */
	private final String url;
	/**
	 * <p>请求连接</p>
	 */
	private final HttpURLConnection httpURLConnection;
	/**
	 * <p>状态码</p>
	 */
	private int code;
	/**
	 * <p>响应头</p>
	 */
	private HttpHeaderWrapper httpHeaderWrapper;
	
	/**
	 * @param url 请求地址
	 * @param connectTimeout 连接超时时间(毫秒)
	 * @param receiveTimeout 响应超时时间(毫秒)
	 * 
	 * @throws NetException 网络异常
	 */
	private HttpClient(String url, int connectTimeout, int receiveTimeout) throws NetException {
		this.url = url;
		this.httpURLConnection = this.buildHttpURLConnection(connectTimeout, receiveTimeout);
		this.buildDefaultHeader();
	}
	
	/**
	 * <p>新建下载HTTP客户端</p>
	 * 
	 * @param url 请求地址
	 * 
	 * @return {@link HttpClient}
	 * 
	 * @throws NetException 网络异常
	 */
	public static final HttpClient newDownloader(String url) throws NetException {
		return newInstance(url, SystemConfig.CONNECT_TIMEOUT_MILLIS, SystemConfig.DOWNLOAD_TIMEOUT_MILLIS);
	}
	
	/**
	 * <p>新建HTTP客户端</p>
	 * 
	 * @param url 请求地址
	 * 
	 * @return {@link HttpClient}
	 * 
	 * @throws NetException 网络异常
	 */
	public static final HttpClient newInstance(String url) throws NetException {
		return newInstance(url, SystemConfig.CONNECT_TIMEOUT_MILLIS, SystemConfig.RECEIVE_TIMEOUT_MILLIS);
	}
	
	/**
	 * <p>新建HTTP客户端</p>
	 * 
	 * @param url 请求地址
	 * @param connectTimeout 连接超时时间(毫秒)
	 * @param receiveTimeout 响应超时时间(毫秒)
	 * 
	 * @return {@link HttpClient}
	 * 
	 * @throws NetException 网络异常
	 */
	public static final HttpClient newInstance(String url, int connectTimeout, int receiveTimeout) throws NetException {
		return new HttpClient(url, connectTimeout, receiveTimeout);
	}
	
	/**
	 * <p>使用缓存</p>
	 * 
	 * @return {@link HttpClient}
	 */
	public HttpClient cache() {
		this.httpURLConnection.setUseCaches(true);
		return this;
	}
	
	/**
	 * <p>设置请求头</p>
	 * 
	 * @param key 请求头名称
	 * @param value 请求头值
	 * 
	 * @return {@link HttpClient}
	 */
	public HttpClient header(String key, String value) {
		this.httpURLConnection.setRequestProperty(key, value);
		return this;
	}
	
	/**
	 * <p>启用长连接(默认)</p>
	 * 
	 * @return {@link HttpClient}
	 */
	public HttpClient keepAlive() {
		return this.header("Connection", "keep-alive");
	}
	
	/**
	 * <p>禁用长连接</p>
	 * 
	 * @return {@link HttpClient}
	 */
	public HttpClient disableKeepAlive() {
		return this.header("Connection", "close");
	}
	
	/**
	 * <p>设置请求范围</p>
	 * 
	 * @param pos 开始位置
	 * 
	 * @return {@link HttpClient}
	 */
	public HttpClient range(long pos) {
		return this.header(HttpHeaderWrapper.HEADER_RANGE, "bytes=" + pos + "-");
	}
	
	/**
	 * <p>执行GET请求</p>
	 * 
	 * @return {@link HttpClient}
	 * 
	 * @throws NetException 网络异常
	 */
	public HttpClient get() throws NetException {
		return this.execute(Method.GET, null);
	}
	
	/**
	 * <p>执行HEAD请求</p>
	 * 
	 * @return {@link HttpClient}
	 * 
	 * @throws NetException 网络异常
	 */
	public HttpClient head() throws NetException {
		return this.execute(Method.HEAD, null);
	}
	
	/**
	 * <p>执行POST请求</p>
	 * 
	 * @param data 请求数据
	 * 
	 * @return {@link HttpClient}
	 * 
	 * @throws NetException 网络异常
	 */
	public HttpClient post(String data) throws NetException {
		this.header(HttpHeaderWrapper.HEADER_CONTENT_TYPE, "application/json");
		return this.execute(Method.POST, data);
	}
	
	/**
	 * <p>执行POST表单请求</p>
	 * 
	 * @param data 请求数据
	 * 
	 * @return {@link HttpClient}
	 * 
	 * @throws NetException 网络异常
	 */
	public HttpClient post(Map<String, String> data) throws NetException {
		// 设置表单请求
		this.header(HttpHeaderWrapper.HEADER_CONTENT_TYPE, "application/x-www-form-urlencoded;charset=" + SystemConfig.DEFAULT_CHARSET);
		if(MapUtils.isEmpty(data)) {
			return this.execute(Method.POST, null);
		} else {
			// 请求表单数据
			final String body = MapUtils.toUrlQuery(data);
			return this.execute(Method.POST, body);
		}
	}
	
	/**
	 * <p>执行请求</p>
	 * 
	 * @param method 请求方法
	 * @param body 请求数据
	 * 
	 * @return {@link HttpClient}
	 * 
	 * @throws NetException 网络异常
	 */
	public HttpClient execute(Method method, String body) throws NetException {
		OutputStream output = null;
		try {
			// 设置请求方式
			this.httpURLConnection.setRequestMethod(method.name());
			if(method == Method.GET) {
				// 是否写出:GET不要写出
				this.httpURLConnection.setDoOutput(false);
			} else if(method == Method.HEAD) {
				// 是否写出:HEAD不要写出
				this.httpURLConnection.setDoOutput(false);
			} else if(method == Method.POST) {
				// 是否写出:POST需要写出
				this.httpURLConnection.setDoOutput(true);
			} else {
				throw new NetException("不支持的请求方式:" + method);
			}
			// 发起连接
			this.httpURLConnection.connect();
			// 发送请求参数
			if(body != null) {
				output = this.httpURLConnection.getOutputStream();
				output.write(body.getBytes());
			}
			// 设置状态码
			this.code = this.httpURLConnection.getResponseCode();
		} catch (IOException e) {
			throw new NetException(e);
		} finally {
			IoUtils.close(output);
		}
		return this;
	}
	
	/**
	 * <p>获取状态码</p>
	 * 
	 * @return 状态码
	 */
	public int code() {
		return this.code;
	}
	
	/**
	 * <p>判断状态码是否成功</p>
	 * 
	 * @return 是否成功
	 * 
	 * @see HttpURLConnection#HTTP_OK
	 */
	public boolean ok() {
		return HttpURLConnection.HTTP_OK == this.code;
	}
	
	/**
	 * <p>判断状态码是否部分内容</p>
	 * 
	 * @return 是否部分内容
	 * 
	 * @see HttpURLConnection#HTTP_PARTIAL
	 */
	public boolean partial() {
		return HttpURLConnection.HTTP_PARTIAL == this.code;
	}
	
	/**
	 * <p>判断状态码是否服务器错误</p>
	 * 
	 * @return 是否服务器错误
	 * 
	 * @see HttpURLConnection#HTTP_INTERNAL_ERROR
	 */
	public boolean internalError() {
		return HttpURLConnection.HTTP_INTERNAL_ERROR == this.code;
	}
	
	/**
	 * <p>判断是否可以下载</p>
	 * 
	 * @return 是否可以下载
	 * 
	 * @see #ok()
	 * @see #partial()
	 */
	public boolean downloadable() {
		return this.ok() || this.partial();
	}
	
	/**
	 * <p>获取响应数据流</p>
	 * <p>使用完成需要关闭(归还连接):下次相同地址端口继续使用(复用底层Socket)</p>
	 * 
	 * @return 响应数据流
	 * 
	 * @throws NetException 网络异常
	 */
	public InputStream response() throws NetException {
		try {
			return this.httpURLConnection.getInputStream();
		} catch (IOException e) {
			throw new NetException(e);
		}
	}

	/**
	 * <p>获取响应字节数组</p>
	 * 
	 * @return 响应字节数组
	 * 
	 * @throws NetException 网络异常
	 */
	public byte[] responseToBytes() throws NetException {
		final var input = this.response();
		try {
			final int size = input.available();
			final var bytes = new byte[size];
			final int length = input.read(bytes);
			if(length == size) {
				return bytes;
			} else {
				return Arrays.copyOf(bytes, length);
			}
		} catch (IOException e) {
			throw new NetException(e);
		} finally {
			IoUtils.close(input);
		}
	}
	
	/**
	 * <p>获取响应文本</p>
	 * 
	 * @return 响应文本
	 * 
	 * @throws NetException 网络异常
	 */
	public String responseToString() throws NetException {
		int length;
		final var input = this.response();
		final var bytes = new byte[SystemConfig.DEFAULT_EXCHANGE_LENGTH];
		final var builder = new StringBuilder();
		try {
			while((length = input.read(bytes)) >= 0) {
				builder.append(new String(bytes, 0, length));
			}
		} catch (IOException e) {
			throw new NetException(e);
		} finally {
			IoUtils.close(input);
		}
		return builder.toString();
	}
	
	/**
	 * <p>获取响应头</p>
	 * 
	 * @return 响应头
	 */
	public HttpHeaderWrapper responseHeader() {
		if(this.httpHeaderWrapper == null) {
			this.httpHeaderWrapper = HttpHeaderWrapper.newInstance(this.httpURLConnection.getHeaderFields());
		}
		return this.httpHeaderWrapper;
	}
	
	/**
	 * <p>关闭连接</p>
	 * <p>关闭连接和底层Socket:不能保持长连接</p>
	 * 
	 * @return {@link HttpClient}
	 */
	public HttpClient shutdown() {
		this.httpURLConnection.disconnect();
		return this;
	}
	
	/**
	 * <p>新建请求连接</p>
	 * 
	 * @param connectTimeout 连接超时时间(毫秒)
	 * @param receiveTimeout 响应超时时间(毫秒)
	 * 
	 * @return 请求连接
	 * 
	 * @throws NetException 网络异常
	 */
	private HttpURLConnection buildHttpURLConnection(int connectTimeout, int receiveTimeout) throws NetException {
		try {
			final var requestUrl = new URL(this.url);
			final var connection = (HttpURLConnection) requestUrl.openConnection();
			// 是否读取
			connection.setDoInput(true);
			// 是否缓存
			connection.setUseCaches(false);
			// 响应超时时间
			connection.setReadTimeout(receiveTimeout);
			// 连接超时时间
			connection.setConnectTimeout(connectTimeout);
			// 是否自动重定向
			connection.setInstanceFollowRedirects(true);
			return connection;
		} catch (IOException e) {
			throw new NetException(e);
		}
	}
	
	/**
	 * <p>设置默认请求头</p>
	 */
	private void buildDefaultHeader() {
		// 接收所有类型参数
		this.header("Accept", "*/*");
		// 设置客户端信息
		this.header(HttpHeaderWrapper.HEADER_USER_AGENT, USER_AGENT);
	}

	/**
	 * <p>新建SSLContext</p>
	 * 
	 * @return {@link SSLContext}
	 */
	private static final SSLContext buildSSLContext() {
		try {
			// SSL协议:SSL、SSLv2、SSLv3、TLS、TLSv1、TLSv1.1、TLSv1.2、TLSv1.3
			final SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
			sslContext.init(null, new X509TrustManager[] { SnailTrustManager.INSTANCE }, NumberUtils.random());
			return sslContext;
		} catch (KeyManagementException | NoSuchAlgorithmException e) {
			LOGGER.error("新建SSLContext异常", e);
		}
		try {
			return SSLContext.getDefault();
		} catch (NoSuchAlgorithmException e) {
			LOGGER.error("新建SSLContext异常", e);
		}
		return null;
	}

	/**
	 * <p>域名验证</p>
	 * 
	 * @author acgist
	 */
	public static class SnailHostnameVerifier implements HostnameVerifier {

		private static final SnailHostnameVerifier INSTANCE = new SnailHostnameVerifier();
		
		private SnailHostnameVerifier() {
		}
		
		@Override
		public boolean verify(String requestHost, SSLSession remoteSslSession) {
			// 证书域名必须匹配
			return requestHost.equalsIgnoreCase(remoteSslSession.getPeerHost());
		}
		
	}
	
	/**
	 * <p>证书验证</p>
	 * 
	 * @author acgist
	 */
	public static class SnailTrustManager implements X509TrustManager {

		private static final SnailTrustManager INSTANCE = new SnailTrustManager();

		private SnailTrustManager() {
		}
		
		@Override
		public X509Certificate[] getAcceptedIssuers() {
			return new X509Certificate[0];
		}

		@Override
		public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
			if(chain == null) {
				throw new CertificateException("证书验证失败");
			}
		}
		
		@Override
		public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
			if(chain == null) {
				throw new CertificateException("证书验证失败");
			}
		}
		
	}
	
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值