HTTP(超文本传输协议)是一种应用层协议,用于客户端和服务端进行通信,按照标准格式如JSON、XML等进行网络数据的传输,通常也作为应用程序之间以RESTAPI形式进行通信的常用协议。
在Java应用中需要调用其他应用提供的HTTP服务API时,通常需要使用一些HTTP客户端组件。
Java原生HttpURLConnection
URLConnection
抽象类URLConnection是表示应用程序和 URL 之间的通信链接的所有类的超类。此类的实例可用于读取和写入 URL 引用的资源。
public abstract class URLConnection {
//访问地址的URL
protected URL url;
// 设置是否向HttpURLConnection输出
protected boolean doInput = true;
// 设置是否从HttpUrlConnection读入
protected boolean doOutput = false;
//是否允许用户交互(例如弹出身份验证对话框)有意义的上下文中检查此URL 。
private static boolean defaultAllowUserInteraction = false;
protected boolean allowUserInteraction = defaultAllowUserInteraction;
// 设置是否使用缓存,默认是使用
private static boolean defaultUseCaches = true;
protected boolean useCaches = defaultUseCaches;
//一些协议支持跳过对象的获取,除非对象在某个时间之后被修改过。默认必须始终进行提取。
protected long ifModifiedSince = 0;
//如果为false ,则此连接对象尚未创建到指定 URL 的通信链接。如果为true ,则通信链路已建立。
protected boolean connected = false;
// 设置超时时间
private int connectTimeout;
private int readTimeout;
//请求头
private MessageHeader requests;
private static volatile FileNameMap fileNameMap;
//如果此类连接尚未建立,则打开指向此 URL 引用的资源的通信链接。
//如果在连接已打开时调用connect方法(由值为true的connected字段指示),则忽略该调用。
abstract public void connect() throws IOException;
//从此打开的连接中读取的输入流
public InputStream getInputStream() throws IOException {
throw new UnknownServiceException("protocol doesn't support input");
}
//写入此连接的输出流。
public OutputStream getOutputStream() throws IOException {
throw new UnknownServiceException("protocol doesn't support output");
}
//设置一般请求属性。
//注意:有多个具有相同键的实例的请求属性使用逗号分隔的列表语法,将多个属性附加到单个属性中。
public void setRequestProperty(String key, String value) {
if (connected)
throw new IllegalStateException("Already connected");
if (key == null)
throw new NullPointerException ("key is null");
if (requests == null)
requests = new MessageHeader();
requests.set(key, value);
}
}
HttpURLConnection :支持 HTTP 特定功能的 URLConnection。Java提供的发起HTTP请求的基础类库,提供了HTTP请求的基本能力
任何网络连接都需要经过socket才能连接,HttpURLConnection不需要设置socket,HttpURLConnection并不是底层的连接,而是在底层连接上的一个请求。
所以HttpURLConneciton只是一个抽象类,自身不能被实例化。
HttpURLConnection
只能通过URL.openConnection()
方法创建具体的实例。
abstract public class HttpURLConnection extends URLConnection {
//默认方法是 GET
protected String method = "GET";
//HTTP 状态码
protected int responseCode = -1;
//响应消息
protected String responseMessage = null;
//如果为true ,协议将自动遵循重定向。如果为false ,协议将不会自动跟随重定向。
private static boolean followRedirects = true;
protected boolean instanceFollowRedirects = followRedirects;
public void setInstanceFollowRedirects(boolean followRedirects) {
instanceFollowRedirects = followRedirects;
}
public boolean getInstanceFollowRedirects() {
return instanceFollowRedirects;
}
//合法的HTTP请求方法
private static final String[] methods = {
"GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"
};
//设置 URL 请求的方法,默认方法是 GET。
public void setRequestMethod(String method) throws ProtocolException {
if (connected) {
throw new ProtocolException("Can't reset method: already connected");
}
for (int i = 0; i < methods.length; i++) {
if (methods[i].equals(method)) {
if (method.equals("TRACE")) {
SecurityManager s = System.getSecurityManager();
if (s != null) {
s.checkPermission(new NetPermission("allowHttpTrace"));
}
}
this.method = method;
return;
}
}
throw new ProtocolException("Invalid HTTP method: " + method);
}
//从 HTTP 响应消息中获取状态代码。如果无法从响应中识别出任何代码(即响应不是有效的 HTTP),则返回 -1。
public int getResponseCode() throws IOException {
if (responseCode != -1) {
return responseCode;
}
Exception exc = null;
try {
getInputStream();
} catch (Exception e) {
exc = e;
}
String statusLine = getHeaderField(0);
if (statusLine == null) {
if (exc != null) {
if (exc instanceof RuntimeException)
throw (RuntimeException)exc;
else
throw (IOException)exc;
}
return -1;
}
if (statusLine.startsWith("HTTP/1.")) {
int codePos = statusLine.indexOf(' ');
if (codePos > 0) {
int phrasePos = statusLine.indexOf(' ', codePos+1);
if (phrasePos > 0 && phrasePos < statusLine.length()) {
responseMessage = statusLine.substring(phrasePos+1);
}
if (phrasePos < 0)
phrasePos = statusLine.length();
try {
responseCode = Integer.parseInt
(statusLine.substring(codePos+1, phrasePos));
return responseCode;
} catch (NumberFormatException e) { }
}
}
return -1;
}
//获取与响应代码一起从服务器返回的 HTTP 响应消息(如果有)
public String getResponseMessage() throws IOException {
getResponseCode();
return responseMessage;
}
@SuppressWarnings("deprecation")
public long getHeaderFieldDate(String name, long Default) {
String dateString = getHeaderField(name);
try {
if (dateString.indexOf("GMT") == -1) {
dateString = dateString+" GMT";
}
return Date.parse(dateString);
} catch (Exception e) {
}
return Default;
}
//断开连接
public abstract void disconnect();
//使用代理
public abstract boolean usingProxy();
//HTTP1.1的状态码
public static final int HTTP_OK = 200;
public static final int HTTP_CREATED = 201;
public static final int HTTP_ACCEPTED = 202;
public static final int HTTP_NOT_AUTHORITATIVE = 203;
public static final int HTTP_NO_CONTENT = 204;
public static final int HTTP_RESET = 205;
public static final int HTTP_PARTIAL = 206;
public static final int HTTP_MULT_CHOICE = 300;
public static final int HTTP_MOVED_PERM = 301;
public static final int HTTP_MOVED_TEMP = 302;
public static final int HTTP_SEE_OTHER = 303;
public static final int HTTP_NOT_MODIFIED = 304;
public static final int HTTP_USE_PROXY = 305;
public static final int HTTP_BAD_REQUEST = 400;
public static final int HTTP_UNAUTHORIZED = 401;
public static final int HTTP_PAYMENT_REQUIRED = 402;
public static final int HTTP_FORBIDDEN = 403;
public static final int HTTP_NOT_FOUND = 404;
public static final int HTTP_BAD_METHOD = 405;
public static final int HTTP_NOT_ACCEPTABLE = 406;
public static final int HTTP_PROXY_AUTH = 407;
public static final int HTTP_CLIENT_TIMEOUT = 408;
public static final int HTTP_CONFLICT = 409;
public static final int HTTP_GONE = 410;
public static final int HTTP_LENGTH_REQUIRED = 411;
public static final int HTTP_PRECON_FAILED = 412;
public static final int HTTP_ENTITY_TOO_LARGE = 413;
public static final int HTTP_REQ_TOO_LONG = 414;
public static final int HTTP_UNSUPPORTED_TYPE = 415;
@Deprecated
public static final int HTTP_SERVER_ERROR = 500;
public static final int HTTP_INTERNAL_ERROR = 500;
public static final int HTTP_NOT_IMPLEMENTED = 501;
public static final int HTTP_BAD_GATEWAY = 502;
public static final int HTTP_UNAVAILABLE = 503;
public static final int HTTP_GATEWAY_TIMEOUT = 504;
public static final int HTTP_VERSION = 505;
}
HTTP常见状态码:https://blog.csdn.net/wounler/article/details/120269477
HttpsURLConnection
:支持 HTTPS 特定功能的 URLConnection。
public abstract class HttpsURLConnection extends HttpURLConnection {
private static HostnameVerifier defaultHostnameVerifier = new DefaultHostnameVerifier();
protected HostnameVerifier hostnameVerifier;
private static SSLSocketFactory defaultSSLSocketFactory = null;
private SSLSocketFactory sslSocketFactory;
protected HttpsURLConnection(URL var1) {
super(var1);
this.hostnameVerifier = defaultHostnameVerifier;
this.sslSocketFactory = getDefaultSSLSocketFactory();
}
public abstract String getCipherSuite();
public abstract Certificate[] getLocalCertificates();
public abstract Certificate[] getServerCertificates() throws SSLPeerUnverifiedException;
public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
Certificate[] var1 = this.getServerCertificates();
return ((X509Certificate)var1[0]).getSubjectX500Principal();
}
public Principal getLocalPrincipal() {
Certificate[] var1 = this.getLocalCertificates();
return var1 != null ? ((X509Certificate)var1[0]).getSubjectX500Principal() : null;
}
public static void setDefaultHostnameVerifier(HostnameVerifier var0) {
if (var0 == null) {
throw new IllegalArgumentException("no default HostnameVerifier specified");
} else {
SecurityManager var1 = System.getSecurityManager();
if (var1 != null) {
var1.checkPermission(new SSLPermission("setHostnameVerifier"));
}
defaultHostnameVerifier = var0;
}
}
public static HostnameVerifier getDefaultHostnameVerifier() {
return defaultHostnameVerifier;
}
public void setHostnameVerifier(HostnameVerifier var1) {
if (var1 == null) {
throw new IllegalArgumentException("no HostnameVerifier specified");
} else {
this.hostnameVerifier = var1;
}
}
public HostnameVerifier getHostnameVerifier() {
return this.hostnameVerifier;
}
public static void setDefaultSSLSocketFactory(SSLSocketFactory var0) {
if (var0 == null) {
throw new IllegalArgumentException("no default SSLSocketFactory specified");
} else {
SecurityManager var1 = System.getSecurityManager();
if (var1 != null) {
var1.checkSetFactory();
}
defaultSSLSocketFactory = var0;
}
}
public static SSLSocketFactory getDefaultSSLSocketFactory() {
if (defaultSSLSocketFactory == null) {
defaultSSLSocketFactory = (SSLSocketFactory)SSLSocketFactory.getDefault();
}
return defaultSSLSocketFactory;
}
public void setSSLSocketFactory(SSLSocketFactory var1) {
if (var1 == null) {
throw new IllegalArgumentException("no SSLSocketFactory specified");
} else {
SecurityManager var2 = System.getSecurityManager();
if (var2 != null) {
var2.checkSetFactory();
}
this.sslSocketFactory = var1;
}
}
public SSLSocketFactory getSSLSocketFactory() {
return this.sslSocketFactory;
}
private static class DefaultHostnameVerifier implements HostnameVerifier {
private DefaultHostnameVerifier() {
}
public boolean verify(String var1, SSLSession var2) {
return false;
}
}
}
JarURLConnection
:与 Java ARchive (JAR) 文件或 JAR 文件中的条目的 URL 连接。
public abstract class JarURLConnection extends URLConnection {
private URL jarFileURL;
private String entryName;
protected URLConnection jarFileURLConnection;
protected JarURLConnection(URL url) throws MalformedURLException {
super(url);
parseSpecs(url);
}
private void parseSpecs(URL url) throws MalformedURLException {
String spec = url.getFile();
int separator = spec.indexOf("!/");
if (separator == -1) {
throw new MalformedURLException("no !/ found in url spec:" + spec);
}
jarFileURL = new URL(spec.substring(0, separator++));
entryName = null;
if (++separator != spec.length()) {
entryName = spec.substring(separator, spec.length());
entryName = ParseUtil.decode (entryName);
}
}
public URL getJarFileURL() {
return jarFileURL;
}
public String getEntryName() {
return entryName;
}
public abstract JarFile getJarFile() throws IOException;
public Manifest getManifest() throws IOException {
return getJarFile().getManifest();
}
public JarEntry getJarEntry() throws IOException {
return getJarFile().getJarEntry(entryName);
}
public Attributes getAttributes() throws IOException {
JarEntry e = getJarEntry();
return e != null ? e.getAttributes() : null;
}
public Attributes getMainAttributes() throws IOException {
Manifest man = getManifest();
return man != null ? man.getMainAttributes() : null;
}
public java.security.cert.Certificate[] getCertificates()
throws IOException
{
JarEntry e = getJarEntry();
return e != null ? e.getCertificates() : null;
}
}
HttpURLConnection进行POST请求例子:
try {
// 1. 获取请求地址
URL url = new URL("请求地址");
// 2. 得到网络访问对象java.net.HttpURLConnection
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 3. 设置请求参数(过期时间,输入、输出流、访问方式),以流的形式进行连接
connection.setDoOutput(false);
connection.setDoInput(true);
connection.setRequestMethod("POST");
connection.setUseCaches(true);
connection.setInstanceFollowRedirects(true);
connection.setConnectTimeout(3000);
connection.setRequestProperty("Content-Type", "application/json;charset=utf-8");
connection.connect();
//4. 入参处理
String body = "入参";
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(connection.getOutputStream(), "UTF-8"));
writer.write(body);
writer.close();
// 5. 得到响应状态码的返回值 responseCode
int code = connection.getResponseCode();
// 6. 如果返回值正常,数据在网络中是以流的形式得到服务端返回的数据
String msg = "";
if (code == 200) { // 正常响应
BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) { // 循环从流中读取
msg += line + "\n";
}
reader.close(); // 关闭流
}
// 7. 断开连接,释放资源
connection.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
注:底层的网络连接可以被多个HttpURLConnection实例共享,但每一个HttpURLConnection实例只能发送一个请求。
请求结束之后,应该调用HttpURLConnection实例的InputStream或OutputStream的close()
方法以释放请求的网络资源,不过这种方式对于持久化连接没用。对于持久化连接,得用disconnect()
方法关闭底层连接的socket。
HttpURLConnection相关博客推荐
上传文件:https://blog.csdn.net/u010957645/article/details/86062741
下载文件:https://blog.csdn.net/zhoukun1008/article/details/79569942
ps:FileOutputStream读取流的时候如果是文件夹,就会出错,无论怎么读,都拒绝访问,应该在读取的目录后面加上文件名!
调用链路源码分析可以参考:
https://blog.csdn.net/u012504392/article/details/109432655