java.net 中只有URLConnection类。具体子类都在sun.net中。
URLConnection和URL的区别:
提供对HTTP首部的访问。
可以配置发送给服务器的请求参数。
还可以向服务器写入数据。
读取服务器数据
读取首部(response)
获取指定首部
public String getContentType()
public int getContentLength()
public String getContentEncoding()
public long getDate()
public long getExpiration()
public long getLastModified()
package Ch7;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
public class AllHeaders {
public static void main(String[] args) {
for(int i=0; i<args.length; i++){
try{
URL u = new URL(args[i]);
URLConnection uc = u.openConnection();
for(int j=1;;j++){
//getHeaderField(String name),返回指定类型首部段的值
//getHeaderField(int n),返回第n个首部段的值
String header = uc.getHeaderField(j);
if(header==null) break;
System.out.println(uc.getHeaderFieldKey(j)+":"+header);
}
}catch (MalformedURLException ex){
System.out.println(args[i]+"is not a URL I understand");
}catch (IOException ex){
System.err.println(ex);
}
System.out.println();
}
}
}
/*Server:Apache
Location:https://www.oreilly.com/
Content-Length:232
Content-Type:text/html; charset=iso-8859-1
Cache-Control:max-age=1316
Expires:Wed, 29 Nov 2017 05:37:00 GMT
Date:Wed, 29 Nov 2017 05:15:04 GMT
Connection:keep-alive
*/
获取任意首部子段
pubilc String getHeaderField(String name)
public String getHeaderFieldKey(int n)
public String getHeaderField(int n)
public long getHeaderFieldDate(String name, long default)
public int getHeaderFieldInt(String name , int default)
缓存
一般情况,使用GET通过HTTP访问的页面应该缓存。使用HTTPS和POST的不缓存。
HTTP控制缓存的首部:
Expires(HTTP 1.0) 缓存直到指定的时间
Cache-control(HTTP 1.1) 会覆盖Expires
- max-age=[seconds]
- s-maxage=[seconds] 在共享缓存的时间
- public : 可以缓存经过认证的响应
- private : 单个用户缓存可以保存。共享缓存不保存。
- no-cache : 仍可以缓存。有附加条件,用Etag或Last-modified首部重新验证响应的状态。
- no-store : 无论如何,都不缓存。
Etag(电子标签),资源改变时,这个资源的唯一标识符。HEAD请求检查服务器端的Etag,若本地缓存和服务器端不同,才会真的执行GET来获取资源。Last-modified同理。
/*************************************解析cache-control************************/
package Ch7;
import java.util.Date;
import java.util.Locale;
public class CacheControl {
private Date maxAge = null;
private Date sMaxAge = null;
private boolean mustRevalidate = false;
private boolean nocache = false;
private boolean nostore = false;
private boolean proxyRevalidate = false;
private boolean publicCache = false;
private boolean privateCache = false;
public CacheControl(String s) {
//没有cach-control即默认策略
if (s==null||!s.contains(":")){
return;}
//取得值
String value = s.split(":")[1].trim();
String[] components = value.split(",");
Date now = new Date();
for(String component: components){
try{
component = component.trim().toLowerCase(Locale.US);
if(component.startsWith("max-age=")){
int secondsInTheFuture = Integer.parseInt(component.substring(8));
maxAge = new Date(now.getTime()+1000*secondsInTheFuture);
}else if(component.startsWith("s-maxage=")){
int secondsInTheFuture = Integer.parseInt(component.substring(8));
sMaxAge = new Date(now.getTime()+1000*secondsInTheFuture);
} else if(component.equals("must-revalidate")){
mustRevalidate = true;
} else if(component.equals("proxy-revalidate")){
proxyRevalidate = true;
} else if(component.equals("no-cache")){
nocache = true;
}else if(component.equals("public")){
publicCache= true;
}else if(component.equals("private")){
privateCache= true;
}
}catch (RuntimeException ex){
continue;
}
}
}
public Date getMaxAge(){
return maxAge;}
public Date getSharedMaxAge(){
return sMaxAge;}
public boolean mustRevalidate(){
return mustRevalidate;}
public boolean ProxyRevalidate(){
return proxyRevalidate;}
public boolean noStore(){
return nostore;}
public boolean noCache(){
return nocache;}
public boolean PublicCache(){
return publicCache;}
public boolean PrivateCache(){
return privateCache;}
}
Java的Web缓存
默认,Java不完成缓存。需要安装URL类使用的系统级缓存。
ResponseCache的一个具体子类
ResponseCache.setDefault( ResponseCache RC);//修改默认缓存
CacheRequest的一个具体子类
CacheResponse的一个具体子类
/*******************************CacheRequest的一个具体子类**********************/
package Ch7;
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();
//通过该OutputStream,将资源存储到缓存中。
@Override
public OutputStream getBody() throws IOException{
return out;
}
//删除所有out中已有数据。
@Override
public void abort(){
out.reset();
}
public byte[] getData(){
if(out.size()==0) return null;
else return out.toByteArray();
}
}
/***********************CacheResponse的一个具体子类***************************/
//取得资源
package Ch7;
import java.net.*;
import java.io.*;
import java.util.*;
public class SimpleCacheResponse extends CacheResponse {
private final Map<String , List<String>> headers;
private final SimpleCacheRequest request;
private final Date expires;
private final CacheControl control;
public SimpleCacheResponse(SimpleCacheRequest request, URLConnection uc, CacheControl control) throws IOException{
this.request = request;
this.control = control;
this.expires = new Date(uc.getExpiration());
this.headers = Collections.unmodifiableMap(uc.getHeaderFields());
}
//getBody返回获取资源的InputStream
@Override
public InputStream getBody(){
//将关联CacheRequest的Outputstream转为资源后,再转为InputStream
return new ByteArrayInputStream(request.getData());
}
//获取响应头
@Override
public Map<String, List<String>> getHeaders() throws IOException{
return headers;
}
public CacheControl getControl(){
return control;
}
public boolean isExpired(){
Date now = new Date();
if(control.getMaxAge().before(now)) return true;
else if(expires !=null && control.getMaxAge()!=null){
return expires.before(now);
}else return false;
}
}
/************************ResponseCache的一个具体子类***********************/
package Ch7;
import java.io.IOException;
import java.net.*;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class MemoryCache extends ResponseCache {
//存储响应,并设置最大容量
//键URI,值CacheResponse
private final Map<URI, SimpleCacheResponse> responses = new ConcurrentHashMap<>();
private final int maxEntries;
public MemoryCache(){
this(100);
}
public MemoryCache(int maxEntries){
this.maxEntries = maxEntries;
}
//返回CacheRequest,包装了一个OutputStream,URL把读取的缓存数据写入这个输出流。
@Override
public CacheRequest put(URI uri, URLConnection conn) throws IOException{
if(responses.size()>=maxEntries) return null;
CacheControl control = new CacheControl(conn.getHeaderField("Cache-Control"));;
if(control.noStore()){
return null;
}
else if(!conn.getHeaderField(0).startsWith("GET")){
return null;
}
//实际应该包装URLConnection指向的资源。再通过request的方法读取。
SimpleCacheRequest request = new SimpleCacheRequest();
SimpleCacheResponse response = new SimpleCacheResponse(request,conn,control);
responses.put(uri,response);
return request;
}
//取得响应
@Override
public CacheResponse get(URI uri, String requestMethod,
Map<String, List<String>> requestHeaders) 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;
}
}
执行顺序:
URLConnection会在getInputStream时自动调用connect()函数
- //执行顺序是 connection.getInputStream()的时候
- //会去执行ResponseCache的get方法
- //如果Cache中有,那么直接从Cache中取出
- //如果没有,那么就执行put 之后再执行get
- //通过ResponseCache的get的CacheResponse的getBody()来获取InputStreamReader
配置连接
七个保护的实例字段,定义了客户端如何向服务器做出请求。且只能在Connect方法,之前设置。
package Ch7;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
/* procected URL url
url字段指定了这个URLConnection连接的URL。构造函数在创建URLConnection时设置这个字段,此后不能改变。
*/
public class URLPrinter {
public static void main(String[] args) {
try{
URL u = new URL("http://www.oreilly.com/");
URLConnection uc = u.openConnection();
System.out.println(uc.getURL());
}catch (IOException ex){
System.err.println(ex);
}
}
}
//http://www.oreilly.com/
protected | |
---|---|
boolean connected | 连接打开为true,关闭为true。没有直接改变或读取该值的方法。但任何改变连接状态的方法,都会改变该值。如:connect(),getInputStream(),getOutputStream() |
boolean allowUserInteraction | setAllowUserInteraction(boolean ) getAllowUserInteraction() |
boolean doInput | |
doOutput | |
ifModifiedSince | |
useCaches | getUseCaches() , setUseCaches() |
超时
//0永不超时,负数抛出异常,正数就是时间
//控制socket等待建立连接的时间。
public void setConnectTimeout(int timeout)
public int getConnectTimeout()
//控制输入流等待数据到达的时间
public void setReadTimeout(int timeout)
public int getReadTimeout()
配置客户端请求HTTP首部
//打开连接之前使用。
public void setRequestProperty(String name, String value)
//获取属性
getRequestProperty(String name)
//增加属性
addRequestProperty()
//获得所有属性
public Map<String, List<String>> getRequestProperties()
向服务器写入数据
//URLConnection方法,获取输出流。
public OutputStream getOutputStream()
//默认情况URLConnection不允许输出,所以先要setDoOutput(true).请求方法将由GET变为POST
package Ch7;
import Ch4.QueryString;
import java.net.*;
import java.io.*;
public class FormPoster {
private URL url;
private QueryString query = new QueryString();
public FormPoster (URL url){
if(!url.getProtocol().toLowerCase().startsWith("http")){
throw new IllegalArgumentException("Posting only works for http URLS");
}
this.url = url;
}
public void add(String name, String value) throws UnsupportedEncodingException{
query.add(name,value);
}
public URL getURL(){
return this.url;
}
//提交表单数据
public InputStream post() throws IOException{
URLConnection uc = url.openConnection();
uc.setDoOutput(true);
try(OutputStreamWriter out = new OutputStreamWriter(uc.getOutputStream(),"UTF-8")){
//POST行、Content-type首部和Content-length首部
//由URLConnection发送,
//我们只需发送数据。
out.write(query.toString());
out.write("\r\n");
//刷新并关闭流,try语句保证关闭。没关闭不会发送任何数据。
out.flush();
}
return uc.getInputStream();
}
public static void main(String[] args) throws UnsupportedEncodingException{
URL url;
if(args.length>0){
try{
url = new URL(args[0]);
}catch (MalformedURLException ex){
System.err.println("Usage: java FormPoster url");
return;
}
}else {
try{
url = new URL("http://www.cafeaulait.org/books/jnp4/postquery.phtml");
}catch (MalformedURLException ex){
System.err.println(ex);
return;
}
}
FormPoster poster = new FormPoster(url);
poster.add("name","Elliotte Rusty Harold");
poster.add("email","elharo@ibiblio.org");
try(InputStream in = poster.post()){
Reader r = new InputStreamReader(in);
int c;
while((c=r.read())!=-1){
System.out.print((char)c);
}
System.out.println();
}catch (IOException ex){
System.err.println("ex");
}
}
}
HttpURLConnection
是URLConnection的抽象子类。因为构造函数是保护类型。不能直接创建实例。使用http URL构造URL。
URL u = new URL("http://...");
HttoURLConnection http = (HttpURLConnection) u.openConnection();
请求方法
//修改请求方法
public void setRequestMethod(String method) throws protocolException
/*************************HEAD*************************************/
//只返回HTTP首部
package Ch4;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
public class LastModified {
public static void main(String[] args) {
for(int i=0; i<args.length; i++){
try{
URL u = new URL(args[i]);
HttpURLConnection http = (HttpURLConnection) u.openConnection();
http.setRequestMethod("HEAD");
System.out.println(u+"was last modified at"+new Date(http.getLastModified()));
}catch (MalformedURLException ex){
System.err.println(args[i]+"is not a URL I understand");
}catch (IOException ex){
System.err.println(ex);
}
}
System.out.println();
}
}
/*http://www.ibiblio.org/xml/was last modified atTue Apr 06 19:45:29 CST 2010*/
/***************************DELETE*******************************************/
//存在安全风险
/***************************PUT*******************************************/
//存储文件到服务器的抽象层次结构,且不需要知道服务器的如何实际存储。与FTP相反
/***************************OPTIONS*******************************************/
//询问某个特定URL支持那些选项
/***************************TRACE*******************************************/
断开连接
HTTP1.1提供持续连接,即重用socket,发送和接受多个信息。
服务器会超时并关闭连接。
主动关闭 disconnect()。如果这个连接上还有流,disconnect会关闭这些流。
处理服务器响应
//返回响应码,200 ,404
public int getResponseCode() throws IOException
//响应信息
public String getResponseMessage() throws IOException
错误条件
一般地,getInputStream失败后,会在catch块里调用getErrorStream()
重定向
300代表某种重定向,请求的资源不在期望的位置上。
//默认,HttpURLConnection会跟随重定向。
public static boolean getFollowRedirects()
//调用该方法后,会改变此后构造的实例行为
public static void setFollowRedirects(boolean follow)
//单个实例的修改方法
public static boolean getInstanceFollowRedirects()
public static void setInstanceFollowRedirects(boolean follow)
代理
public abstract boolean usingProxy()
//判断是否使用代理
流模式
文件太大时的分块传输。