Java网络编程 Ch7 URLConnection

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()函数

  1. //执行顺序是 connection.getInputStream()的时候
  2. ​ //会去执行ResponseCache的get方法
  3. ​ //如果Cache中有,那么直接从Cache中取出
  4. ​ //如果没有,那么就执行put 之后再执行get
  5. ​ //通过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 allowUserInteractionsetAllowUserInteraction(boolean ) getAllowUserInteraction()
boolean doInput
doOutput
ifModifiedSince
useCachesgetUseCaches() , 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");
        }
    }
}

clipboard

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()
  //判断是否使用代理

流模式

文件太大时的分块传输。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值