JAVA网络编程-URLConnection
抽象的URLConnection
构建URLConnection
读取服务器的数据
读取首部
获取全部header
缓存
Java的Web缓存
属性设置
配置客户端请求HTTP首部
特殊的首部Cookie
向服务器写入数据
HTTPURLConnection
抽象的URLConnection
URLConnection是一个抽象类,表示指向URL指定资源的活动连接。URLConnection有两个不同但相关的用途。首先与URL类相比,它与服务器(特别是HTTP服务器)的交互提供了更多的控制。URLConnection可以检查服务器发送的首部,并相应地做出相应。它可以设置客户端中使用的首部字段。最后URLConnection可以用POST,PUT,和其他HTTP请求方法向服务器发回数据。
其次,URLConnection类是Java的协议处理器机制的一部分,这个机制还包括URLStreamHandler类。协议处理器的思想很简单:它将处理器协议的细节与处理特定数据类型分开,提供相应的用户接口,并完成完整的Web浏览器所完成的其他操作。java.net包中只有抽象的URLConnection类。具体的子类都隐藏在sun.net包层次结构中。URLConnection的许多方法和字段及构造函数都是受保护的。很少会在源代码中直接实例化一个URLConnection对象。相反,运行时环境会根据所使用的协议来创建所需的对象,然后使用java.lang.Class类的forName()和newInstance()方法实例化这个类。
构建URLConnection
public static void main(String[] args) throws Exception { //URL u = new URL("http://localhost:8888/test");//sun.net.www.protocol.http.HttpURLConnection URL u = new URL("https://www.cnblogs.com/zumengjie/p/14897556.html");//sun.net.www.protocol.https.HttpsURLConnectionImpl URLConnection uc = u.openConnection(); System.out.println(uc.getClass().getName()); }
RULConnection类声明为抽象类。不过只有一个connect()方法是抽象方法。必须由子类实现,例如sun.net.www.protocol.fileFileURLConnection的connect()方法将URL转换为目录中的一个文件名,创建该文件的MIME信息,然后打开一个指向该文件的缓冲FileInputStream。sun.net.www.protocol.http.HttpURLConnection的connect()方法会创建一个sun.net.www.http.HttpClient对象,由它连接服务器。
第一次构造URLConnection时,它是未连接的。也就是说,本地和远程主机无法发送和接收数据。没有socket连接这两个主机。connect()方法在本机和远程主机之间建立一个连接(一般使用TCP scoket)。调用getInputStream()getContent()getHeaderField()和其他要求打开连接的方法,如果连接尚未打开,它们就会调用connect()。
读取服务器的数据
@GetMapping("test") public String test() { return "success"; }
public static void main(String[] args) throws Exception { URL u = new URL("http://localhost:8888/test");// sun.net.www.protocol.http.HttpURLConnection URLConnection uc = u.openConnection(); InputStream is = uc.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); String s = null; while ((s = br.readLine()) != null) { System.out.println(s); } }
读取首部
getContentType():返回相应主体的MIME内容类型。如何没有提供则返回null如果内容是某种形式的文本,那么首部还会包含一个字符集部分来标识文档的字符编码方式。
getContentLength():返回内容中有多少子节,如果没有指定则返回-1
getContentLengthLong():Java7的方法和上述方法类型为了防止返回内容过大超出int范围
getContentEncoding():返回内容编码,注意内容编码和字符编码是两回事,内容编码指出子节如何编码未其他子节。一般没有或者是x-gzip
getDate():获取文档发出的时间
getLastModified():获取文档最后修改的时间,一般没有
getExpiration():获取文档过期时间,一般没有
@GetMapping("test") public String test(){ return "success"; }
public static void main(String[] args) throws Exception { URL u = new URL("http://localhost:8888/test");// sun.net.www.protocol.http.HttpURLConnection URLConnection uc = u.openConnection(); System.out.println(uc.getContentType());//text/html;charset=UTF-8 System.out.println(uc.getContentLength());//7 System.out.println(uc.getContentLengthLong());//7 System.out.println(uc.getContentEncoding());//null System.out.println(uc.getDate());//1626506920000 System.out.println(uc.getLastModified());//0 System.out.println(uc.getExpiration());//0 }
获取全部header
getHeaderField(str):获取指定key的首部,没有找到返回null
getHeaderFieldKey(index):获取指定下标首部的key,注意key可能为null
getHeaderField(index):获取指定下标的value,value不能为null
public static void main(String[] args) throws Exception { URL u = new URL("http://localhost:8888/test");// sun.net.www.protocol.http.HttpURLConnection URLConnection uc = u.openConnection(); System.out.println(uc.getHeaderField("a")); System.out.println("------------"); for(int i=0; ; i++) { String key = uc.getHeaderFieldKey(i); String value = uc.getHeaderField(i); if(value==null) { break; } System.out.println(key+"--------"+value); } }
1111 ------------ null--------HTTP/1.1 200 a--------1111 b--------xiaxiede Content-Type--------text/html;charset=UTF-8 Content-Length--------7 Date--------Sat, 17 Jul 2021 08:11:38 GMT Keep-Alive--------timeout=60 Connection--------keep-alive
缓存
一些页面上重复出现的图片浏览器一般只会从远程服务器上加载一次,将他保存在缓存中.HTTP首部Expires和Cache-Control可以控制缓存.
默认情况下,一般认为使用GET通过HTTP访问的页面可以缓存.使用HTTPS或POST访问的页面通常不缓存.
Expires首部(主要针对HTTP1.0)指示可以缓存这个资源,直到指定的时间为止.
Cache-control首部(HTTP1.1)提供了更细粒度的缓存策略:
max-age=[seconds]:从现在直到缓存项过期之前的秒数.
s-maxage=[secods]:从现在起,直到缓存项在共享缓存中过期之前的描述,私有缓存可以保存更长时间.
public:可以缓存一个经过认证的相应.否则已认证的相应不能缓存.
private:仅单个用户缓存可以保存相应,而共享缓存不应保存.
no-cache:缓存项仍然可以缓存,不过客户端要在每次访问时用一个Etag或Last-modified首部重新验证相应状态.
no-store:不缓存.
如果Cache-control和Expires首部同时出现,Cache-control会覆盖Expires.服务器可以在一个首部中发送多个Cache-control首部,只要它们没有冲突.
Last-modified首部指示资源最后一次修改的日期.客户端可以使用HEAD请求来检查这个日期,只有当本地缓存的副本早于Last-modified日期时,它才会真正执行GET来获取资源.
Etag首部是资源改变时这个资源唯一标识符.客户端可以使用一个HEAD请求来检查这个标识符.只有当本地缓存的副本有一个不同的ETag时,它才会真正执行GET来获取资源.
@GetMapping(value="test") public String g(HttpServletResponse response) { System.out.println("1111111111111111111111111111111111"); response.addHeader("Cache-control", "max-age=10"); return "success1"; }
<html> <head></head> <body> <form action="http://127.0.0.1:8080/test" method="GET"> <input type="submit" value="发送"/> </form> </body> </html>
注意因为不同浏览器的行为不一致,测试结果可能大不相同,笔者这里测试的方式是自己写表单使用Chrome浏览器,通过点击的方式测试.而非直接在浏览器输入地址后刷新.
Java的Web缓存
默认情况下,Java并不完成缓存.要安装URL类使用的系统级缓存需要有ResponseCache的一个子类,CacheRequest的一个子类,CacheResponse的一个子类.要安装你的ResponseCache子类来处理你的CacheRequest和CacheResponse子类需要把它传递到静态方法ResponseCache.setDefault()这会把这个缓存对象安装为系统的默认缓存.Java虚拟机只支持一个共享缓存.
@GetMapping(value="test") public String g(HttpServletResponse response) { System.out.println("1111111111111111111111111111111111"); response.addHeader("Cache-control", "max-age=60"); return "success1"; }//服务端代码
public class Test { public static void main(String[] args) throws Exception { ResponseCache.setDefault(new MemoryCache()); URL u = new URL("http://localhost:8080/test"); URLConnection uc = u.openConnection(); BufferedReader br = new BufferedReader(new InputStreamReader(uc.getInputStream())); System.out.println(br.readLine()); URLConnection uc2 = u.openConnection(); BufferedReader br2 = new BufferedReader(new InputStreamReader(uc2.getInputStream())); System.out.println(br2.readLine()); Thread.sleep(1000*70); URLConnection uc3 = u.openConnection(); BufferedReader br3 = new BufferedReader(new InputStreamReader(uc3.getInputStream())); System.out.println(br3.readLine()); } }
package com.datang.test.huancun; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; public class CacheControl { private Date maxAge = null; public CacheControl(String s) { int index = s.indexOf("=") + 1; String expires = s.substring(index); Date date = new Date(System.currentTimeMillis()+(Long.parseLong(expires)*1000)); this.maxAge = date; } public Date getMaxAge() { return maxAge; } }
package com.datang.test.huancun; import java.io.IOException; import java.net.CacheRequest; import java.net.CacheResponse; import java.net.ResponseCache; import java.net.URI; import java.net.URLConnection; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class MemoryCache extends ResponseCache { private final Map<URI, SimpleCacheResponse> responses = new ConcurrentHashMap<>(); private final int maxEntries; public MemoryCache() { this(100); } public MemoryCache(int maxEntries) { this.maxEntries = maxEntries; } @Override public CacheResponse get(URI uri, String requestMethod, Map<String, List<String>> rqstHeaders) throws IOException { if ("GET".equals(requestMethod)) { SimpleCacheResponse response = responses.get(uri); if (response != null && response.isExpired()) { responses.remove(response); response = null; } return response; } else { return null; } } @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException { if (responses.size() >= maxEntries) { return null; } CacheControl control = new CacheControl(conn.getHeaderField("Cache-Control")); SimpleCacheRequest request = new SimpleCacheRequest(); SimpleCacheResponse response = new SimpleCacheResponse(request, conn, control); responses.put(uri, response); return request; } }
package com.datang.test.huancun; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.CacheRequest; public class SimpleCacheRequest extends CacheRequest { private ByteArrayOutputStream out = new ByteArrayOutputStream(); @Override public OutputStream getBody() throws IOException { return out; } @Override public void abort() { out.reset(); } public byte[] getData() { if (out.size() == 0) { return null; } else { return out.toByteArray(); } } }
package com.datang.test.huancun; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.CacheResponse; import java.net.URLConnection; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; public class SimpleCacheResponse extends CacheResponse { private final SimpleCacheRequest request; private final Map<String, List<String>> headers; private final CacheControl control; private final Date expires; public SimpleCacheResponse(SimpleCacheRequest request, URLConnection uc, CacheControl control) { this.request = request; this.headers = Collections.unmodifiableMap(uc.getHeaderFields()); this.control = control; this.expires = new Date(uc.getExpiration()); } @Override public Map<String, List<String>> getHeaders() throws IOException { return headers; } @Override public InputStream getBody() throws IOException { return new ByteArrayInputStream(request.getData()); } public CacheControl getControl() { return control; } public boolean isExpired() { Date now = new Date(); if (control.getMaxAge().before(now)) { return true; }else{ return false; } } }
属性设置
connected();获取连接是否已经打开,这个方法只能是URLConnection或它的子类使用.导致URLConnection连接的方法都会将这个变了设置为true,包括connect();getInputStream()和getOutputStream()都会使这个方法的返回值变为true.反过来说,如果只是通过URL获取到了URLConnection并不会打开连接.一些子类的disconnect();会使该值变为false.
getURL();获取构建URLConnection的URL
public static void main(String[] args) throws Exception { URL u = new URL("http://localhost:8080/test"); URLConnection uc = u.openConnection(); System.out.println(uc.getURL());//显示的就是创建URLConnection所用的URL }
setDoInput(boolean b);默认是true,如果设置成false则不可读
public static void main(String[] args) throws Exception { URL u = new URL("http://localhost:8080/test"); URLConnection uc = u.openConnection(); uc.setDoInput(false);//设置为false则不可读 BufferedReader br = new BufferedReader(new InputStreamReader(uc.getInputStream())); System.out.println(br.readLine()); }
Exception in thread "main" java.net.ProtocolException: Cannot read from URLConnection if doInput=false (call setDoInput(true)) at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1506) at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1498) at com.datang.test.huancun.Test2.main(Test2.java:14)
setIfModifiedSince(long l);设置If-Modified-Since首部,表示客户端获取文档的最后日期,有的服务端会根据这个首部判断是否返回新的文档.如果文档的修改日期在获取日期之后则返回新的文档.
@GetMapping(value="test") public String g(HttpServletResponse response,HttpServletRequest request) { String header = request.getHeader("If-Modified-Since"); System.out.println(header); return "success1"; }//服务端
public static void main(String[] args) throws Exception { URL u = new URL("http://localhost:8080/test"); URLConnection uc = u.openConnection(); long millisecondsPerDay = 24 * 60 * 60 * 1000; uc.setIfModifiedSince(new Date().getTime() +millisecondsPerDay); BufferedReader br = new BufferedReader(new InputStreamReader(uc.getInputStream())); System.out.println(br.readLine()); }//客户端
setUseCaches(boolean b);如果设置为false则禁用缓存.
@GetMapping(value="test") public String g(HttpServletResponse response,HttpServletRequest request) { response.addHeader("Cache-control", "max-age=60"); System.out.println("11111111111111"); return "success1"; }//服务端
public static void main(String[] args) throws Exception { ResponseCache.setDefault(new MemoryCache()); URL u = new URL("http://localhost:8080/test"); URLConnection uc = u.openConnection(); uc.setUseCaches(false); BufferedReader br = new BufferedReader(new InputStreamReader(uc.getInputStream())); System.out.println(br.readLine()); URLConnection uc2 = u.openConnection(); uc2.setUseCaches(false); BufferedReader br2 = new BufferedReader(new InputStreamReader(uc2.getInputStream())); System.out.println(br2.readLine()); URLConnection uc3 = u.openConnection(); uc3.setUseCaches(false); BufferedReader br3 = new BufferedReader(new InputStreamReader(uc3.getInputStream())); System.out.println(br3.readLine()); }//客户端
setConnectionTimeout(long l);设置客户端连接超时时间.
setReadTimeout(long l):设置客户端等待输入流到达时间.
@GetMapping(value="test") public String g(HttpServletResponse response,HttpServletRequest request) { try { Thread.sleep(1000*3); } catch (InterruptedException e) { } return "success1"; }//服务端
public static void main(String[] args) throws Exception { URL u = new URL("http://localhost:8080/test"); URLConnection uc = u.openConnection(); uc.setConnectTimeout(1000); uc.setReadTimeout(1000); BufferedReader br = new BufferedReader(new InputStreamReader(uc.getInputStream())); System.out.println(br.readLine()); }//客户端
配置客户端请求HTTP首部
HTTP客户端向服务器发送请求时都会发送请求行和首部.Chrome浏览器会发送默认的首部.同样URLConnection也会发送默认的首部.
@GetMapping(value="test") public String g(HttpServletResponse response,HttpServletRequest request) { Enumeration<String> headerNames = request.getHeaderNames(); while(headerNames.hasMoreElements()) { String key = headerNames.nextElement(); String value = request.getHeader(key); System.out.println(key+"==="+value); } return "success1"; }//服务端
user-agent===Java/1.8.0_291 host===localhost:8080 accept===text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 connection===keep-alive //服务端获取的header
public static void main(String[] args) throws Exception { URL u = new URL("http://localhost:8080/test"); URLConnection uc = u.openConnection(); BufferedReader br = new BufferedReader(new InputStreamReader(uc.getInputStream())); System.out.println(br.readLine()); }
setRequestProperty(String k,String v);添加请求头,注意两次调用该方法设置同一个key会覆盖.
addRequestProperty(String k,String v);追加请求头,若要一个请求头对应多个value则要使用追加.
getRequestProperty(String k);根据请求头获取value如果有多个value则只获取一个.
Map<String,List<String>> getRequestProperties();获取全部的请求头和对应的值.
public static void main(String[] args) throws Exception { URL u = new URL("http://localhost:8080/test"); URLConnection uc = u.openConnection(); uc.setRequestProperty("test","test1"); uc.setRequestProperty("test","test2"); uc.setRequestProperty("a","a1"); uc.addRequestProperty("a","a2"); System.out.println(uc.getRequestProperty("a")); System.out.println("------------------------"); Map<String, List<String>> requestProperties = uc.getRequestProperties(); Set<Map.Entry<String, List<String>>> entries = requestProperties.entrySet(); for (Map.Entry entry:entries){ String key =(String) entry.getKey(); List<String> values = (List<String>)entry.getValue(); System.out.println(key); values.stream().forEach(System.out::println); System.out.println("----------------------------"); } BufferedReader br = new BufferedReader(new InputStreamReader(uc.getInputStream())); System.out.println(br.readLine()); }//客户端
@GetMapping(value="test") public String g(HttpServletResponse response,HttpServletRequest request) { Enumeration<String> headerNames = request.getHeaderNames(); while(headerNames.hasMoreElements()) { String key = headerNames.nextElement(); Enumeration<String> headers = request.getHeaders(key); while(headers.hasMoreElements()) { String value = headers.nextElement(); System.out.println(key+" "+value); } } return "success1"; }//服务端
特殊的首部Cookie
@GetMapping(value="test") public String g(HttpServletResponse response,HttpServletRequest request) { Cookie[] cookies = request.getCookies(); for(Cookie cookie:cookies) { String name = cookie.getName(); String value = cookie.getValue(); System.out.println(name+":"+value); } return "success1"; }//服务端
public static void main(String[] args) throws Exception { URL u = new URL("http://localhost:8080/test"); URLConnection uc = u.openConnection(); uc.setRequestProperty("Cookie","username=111; password=222; auto=admin"); Map<String, List<String>> requestProperties = uc.getRequestProperties(); Set<Map.Entry<String, List<String>>> entries = requestProperties.entrySet(); for (Map.Entry entry:entries){ String key =(String) entry.getKey(); List<String> values = (List<String>)entry.getValue(); System.out.println(key); values.stream().forEach(System.out::println); System.out.println("----------------------------"); } BufferedReader br = new BufferedReader(new InputStreamReader(uc.getInputStream())); System.out.println(br.readLine()); }//客户端
向服务器写入数据
从URLConnection中获取OutputStream可以用来写入数据传给服务器数据。由于URLConnection在默认请求下不允许输出,所以在输出前必须调用setDoOutput(true);但是需要注意以上两个步骤会将本次请求改变为POST请求。
@PostMapping(value="test") public String g(@RequestBody Person p) { System.out.println(p); return "success1"; }//服务端
public static void main(String[] args) throws Exception { URL u = new URL("http://localhost:8888/test"); URLConnection uc = u.openConnection(); uc.setRequestProperty("Content-Type", "application/json;charset=utf-8"); uc.setDoOutput(true); OutputStream outputStream = uc.getOutputStream(); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream)); bw.append("{\"name\":\"zhangsan\",\"age\":11}"); bw.flush(); BufferedReader br = new BufferedReader(new InputStreamReader(uc.getInputStream())); System.out.println(br.readLine()); }
HTTPURLConnection
如果URL是一个HTTP请求则openConnection()返回的实际上是一个HTTPURLConnection实例。
public static void main(String[] args) throws Exception { URL u = new URL("http://localhost:8888/test"); HttpURLConnection uc = (HttpURLConnection) u.openConnection(); BufferedReader br = new BufferedReader(new InputStreamReader(uc.getInputStream())); System.out.println(br.readLine()); }
使用具体子类的方便处在于可以手动的设定请求方式。setRequestMethod(String m)改变请求方法。可选的参数为:
GET POST HEAD PUT DELETE OPTIONS TRACE
HEAD
HEAD请求方式只用来获取服务器的首部,服务器不会返回请求体。注意!客户端请求方式是HEAD服务的只能是GET或HEAD
public static void main(String[] args) throws Exception { URL u = new URL("http://localhost:8888/test"); HttpURLConnection uc = (HttpURLConnection) u.openConnection(); uc.setRequestMethod("HEAD"); Map<String, List<String>> headerFields = uc.getHeaderFields(); Set<Entry<String, List<String>>> entrySet = headerFields.entrySet(); for (Entry<String, List<String>> entry : entrySet) { String key = entry.getKey(); List<String> value = entry.getValue(); System.out.println(key); for (String v : value) { System.out.println(v); } System.out.println("============="); } BufferedReader br = new BufferedReader(new InputStreamReader(uc.getInputStream())); System.out.println(br.readLine()); }//客户端
Keep-Alive timeout=60 ============= null HTTP/1.1 200 ============= Connection keep-alive ============= Content-Length 8 ============= Date Sat, 24 Jul 2021 02:44:21 GMT ============= Content-Type text/html;charset=UTF-8 ============= null
@GetMapping(value="test") public String g() { return "success1"; }//服务端
@RequestMapping(value="test",method=RequestMethod.HEAD) public String g() { return "success1"; }//服务端
DELETE
语义上来讲DELETE方法将删除Web服务器上位于指定URL的文件。同时需要服务器支持这个方法,但即使服务器接收这个请求,也并非意味着服务器一定是删除了某个资源。
@RequestMapping(value="test",method=RequestMethod.DELETE) public String g() { return "success1"; }//服务端
public static void main(String[] args) throws Exception { URL u = new URL("http://localhost:8888/test"); HttpURLConnection uc = (HttpURLConnection) u.openConnection(); uc.setRequestMethod("DELETE"); BufferedReader br = new BufferedReader(new InputStreamReader(uc.getInputStream())); System.out.println(br.readLine()); }//客户端
PUT
语义上来讲PUT方法将上传数据到Web服务器上位于指定URL的文件。同时需要服务器支持这个方法,但即使服务器接收这个请求,也并非意味着服务器一定做的是新增操作。
public static void main(String[] args) throws Exception { URL u = new URL("http://localhost:8888/test"); HttpURLConnection uc = (HttpURLConnection) u.openConnection(); uc.setRequestMethod("PUT"); BufferedReader br = new BufferedReader(new InputStreamReader(uc.getInputStream())); System.out.println(br.readLine()); }//客户端 }
@RequestMapping(value="test",method=RequestMethod.PUT) public String g() { return "success1"; }//服务端
OPTIONS
OPTIONS请求询问特定的URL支持哪些请求方式。该请求同样不会返回响应体,而是在响应头中反馈Allow告知客户端支持的请求方式。
public static void main(String[] args) throws Exception { URL u = new URL("http://localhost:8888/test"); HttpURLConnection uc = (HttpURLConnection) u.openConnection(); uc.setRequestMethod("OPTIONS"); Map<String, List<String>> headerFields = uc.getHeaderFields(); Set<Entry<String, List<String>>> entrySet = headerFields.entrySet(); for (Entry<String, List<String>> entry : entrySet) { String key = entry.getKey(); List<String> value = entry.getValue(); System.out.println(key); for (String v : value) { System.out.println(v); } System.out.println("============="); } BufferedReader br = new BufferedReader(new InputStreamReader(uc.getInputStream())); System.out.println(br.readLine()); }//客户端
@RequestMapping(value="test",method= {RequestMethod.PUT}) public String g() { return "success1"; }//服务端
Keep-Alive timeout=60 ============= null HTTP/1.1 200 ============= Connection keep-alive ============= Content-Length 0 ============= Date Sat, 24 Jul 2021 03:03:56 GMT ============= Allow PUT,OPTIONS ============= null
TRACE(没有做出对应的demo,先留个坑)