MyException - 我的异常网
当前位置:我的异常网» 编程 » (5) 建立URL连接

(5) 建立URL连接

www.MyException.Cn  网友分享于:2015-08-26  浏览:8次
(五) 建立URL连接

为了在Java程序中访问Web服务器,会希望在更高的级别上进行处理,而不只是创建套接字连接和发送HTTP请求。

1.URL和URI
(1)URL和URLConnection类封装了大量复杂的实现细节,这些细节设计如何从远程站点获取信息。
例:通过传递字符串来构建一个URL对象

    URL url = new URL(urlString);

 

如果只是想获得该资源的内容,可以使用URL类中的openStream方法。该方法返回一个InputStream对象,然后就可以按照一般的用法来使用这个对象了,比如用它构建一个Scanner对象。
例:通过URL构建Scanner对象

    InputStream inStream = url.openStream();
    Scanner in = new Scanner(inStream);
 

(2)java.net包对 统一资源定位符(uniform resource locator, URL) 和 统一资源标识符(uniform resource identifier, URI) 做了非常有用的区分。
URI是个纯粹的句法结构,用于指定标识Web资源的字符串的各个不同部分。URL是URI的一个特例,它包含了用于定位Web资源的足够信息。
其他URI,比如 mailto:cay@horstmann.com 则不属于定位符,因为根据该标识符我们无法定位任何数据。像这样的URI称之为URN(uniform resource name, 统一资源名称)

(3)在java类库中,URI类不包含任何用于访问资源的方法,它的惟一作用就是解析。想法的是,URL类可以打开一个到达资源的流。因此,URL类只能用于那些Java类库知道该如何处理的模式。
URL可以处理的模式包含 http: 、https 、ftp: 、本地文件系统(file:)和JAR文件(jar:)。

(4)URI的解析并不是可有可无的,要考虑到它也许会变的非常复杂。
例: http://maps.yahoo.com/py/maps.py?csz=Cupertino+CA
    ftp://username:password@ftp.yourserver.com/pub/file.txt

URI规范给出了标记这些标识符的规则,一个URI具有以下语法
    [scheme:]schemeSpecificPart[#fragment]
上式中,[...]表示可选部分,它与:和#可以被包含在标识符内。

(5)包含 scheme: 部分的URI被称为绝对URI。否则称为相对URI。

(6)如果绝对URI的schemeSpecificPart不是以/开头的,我们就称它是不透明的。
例: mailto:cay@horstmann.com

(7)所有绝对的透明的URI和所有相对的URI都是有 分层的(hierarchical)
例: http://java.sun.com/index.html
    ../../java/net/Socket.html#Socket()

(8)一个分层的URI的schemeSpecificPart具有以下结构:
[//authority][path][?query]

(9)对于那些基于服务器的URI,authority部分采用以下形式
[user-info@]host[:port]
port必须是一个整数

RFC 2396(标准化URI的文献)还支持一种基于注册表的机制,此时authority采用了一种不同的格式。不过,这种情况并不常见。

(10)URI类的作用之一是解析标识符并将它分解成各种不同的组成部分。可以用一下方法读取它们:
    getScheme
    getSchemeSpecificPart
    getAuthority
    getUserInfo
    getHost
    getPort
    getQuery
    getFragment

(11)URI类的另一个作用是处理绝对标识符和相对标识符。
例: 如果存在一个如下的绝对URI:
    http://docs.mycompany.com/api/java/net/ServerSocket.html
    和一下如下的的相对URI
    ../../java/net/Socket.html#Socket()
    那么可以将它们合并为一个绝对URI
    http://docs.mycompany.com/api/java/net/Socket.html#Socket()
这个过程被称为相对URL的 转换(resolving)。

(12)与此相反的过程称为相对化(relativization)。
例: 有一个基本URI:
    http://docs.mycompany.com/api
    和另一个URI:
    http://docs.mycompany.com/api/java/lang/String.html
    那么相对化之后的URI就是:
    java/lang/String.html
(13)URI类同时支持一下两个操作:

    relative = base.relativize(combined);
    combined = base.resolve(relative);

 

2.使用URLConnection获取信息
如果想从某个Web资源获取更多信息,那么应该使用URLConnection类,它能得到比基本的URL类更多的控制功能。
当操作一个URLConnection对象时,必须像下面这样非常小心的安排操作步骤:
(1)调用URL类中的openConnection方法获得URLConnection对象:

    URLConnection connection = url.openConnection();
 

(2)使用一下方法来设置任意的请求属性
    setDoInput 
    setDoOutput
    setIfModifiedSince
    setUseCaches
    setAllowUserInteraction
    setRequestProperty
    setConnectTimeout
    setReadTimeout

(3)调用connect方法连接远程资源:

    connection.connect();

 

除了与服务器建立套接字连接外,该方法还可以用于向服务器查询头信息(header information)。

(4)与服务器建立连接后,可以查询头信息。getHeaderFieldKey和getHeaderField两个方法列举了消息头的所有字段。
getHeaderFields方法返回一个包含了消息头中所有字段的标准Map对象。为了方便使用,一下方法可以查询各标准字段:
    getContentType
    getContentLength
    getContentEncoding
    getDate
    getExpiration
    getLastModified
   
(5)最后访问资源数据。使用getInputStream方法获取一个输入流用以读取信息(这个输入流与URL类中的openStream方法所返回的流相同)。
另一个方法getContent在实际操作中并不是很有用。有标准内容类型(比如text/plain和image/gif)所返回的对象需要使用com.sun层次结构中的类来进行处理。也可以注册自己的内容处理器。

(6)注意,URLConnection类中的getInputStream和getOutputStream方法与Socket类中的这些方法不同。
URLConnection类具有很多表面之外的功能,尤其在处理请求和相应消息头时。正因为如此,严格遵循建立连接的每个步骤都显得非常重要。

(7)URLConnection类中的一些方法。有几个方法可以在与服务器建立连接之前设置连接属性。
其中最重要的是setDoInput和setDoOutput。
在默认情况下建立连接只有从服务器读取信息的输入流(即setDoInput默认值为true),并没有任何执行写操作的输出流(setDoOutput默认值为false)。如果想获得输出流(例如,向Web服务器提交数据),需要调用:
    connection.setDoOutput(true);

(8)设置某些请求头(request header)。请求头是与请求命令一起发送到服务器的。
例:
    GET www.server.com/index.html HTTP/1.0
    Referer: http://www.sonewhere.com/links.html
    Proxy-Connection: Keep-Alive
    User-Agent: Mozilla/5.0(X11; U; Linux i686; en-US; rv:1.8.1.4)
    Host:www.server.com
    Accept: text/html, image/gif, image/jpeg, image/png, */*
    Accept-Language: en
    Accept_Charset: iso-8859-1,*,utf-8
    Cookie: orangemilano=192218887821987
setIfModifiedSince(long ifmodifiedsince)方法用于告诉连接只对自某个特定日期依赖被修改过的数据该兴趣
setUseCaches(boolean usecaches)和setAllowUserInteraction(boolean allowuserinteraction)这两个方法只用于Applet
setUseCaches方法用于命令浏览器首先检查它的缓存,UseCaches 标志为 true,则允许连接使用任何可用的缓存。如果为 false,则忽略缓存,默认为 true。例如浏览器中的“重新加载”
setAllowUserInteraction方法则用于在访问有密码保护的资源时弹出对话框,以便查询用户名和口令。

(9)一个总揽全局的方法:setRequestProperty,它可以用来设置对特定协议起作用的任何"名-值(name/value)对"。
关于HTTP请求头的格式参加RFC 2616,其中的某些参数没有很好地记录在文档中,它们通常在程序员直接口头传授。
例:访问一个由口令保护的Web也,那么必须按如下步骤操作:
    1)将用户名、磨耗和口令以字符串形式连接在一起。

    String input = username + ":" + password;

     2)计算上一步骤所得字符串的base64编码。(base64编码用于将子就留编码成可打印的ASCII字符流)
      可以通过sun.misc.BASE64Encoder进行编码

    String encoding = new sun.misc.BASE64Encoder().encode(input.getBytes());

 

      注意sun.misc.BASE64Encoder属于未公开(undocumented)的类
    3)调用setRequestProperty方法,设置name参数的值为"Authorization"、value参数的值为"Basic"+encoding;

    connection.setRequestProperty("Authorization", "Basic" + encoding);

 

    4)上述是访问有口令保护的web页,如果想通过FTP访问一个由口令保护的文件时,要采用一种完全不同的方法。可以直接构建一个如下格式的URL:
    ftp://username:password@ftp.yourserver.com/pub/file.txt

(10)一旦调用了connect方法,就可以查询响应头信息。

    列举所有响应头的字段,该操作采用了另一种迭代方式。

    String key = connection.getHeaderFieldKey(n);

 

可以获得响应头的第n个键,其中n从1开始。如果n为0或大于消息头的字段总数,该方法将返回null值。没有哪种方法可以返回字段的数量,必须反复调用getHeaderFieldKey方法直到返回null为止。

    得到第n个值

    String value = connection.getHeaderField(n);

 getHeaderFields方法可以返回一个封装了响应头字段的Map对象。

 

    Map<String, List<String>> headerFields = connection.getHeaderFields();
    for(Map<String, List<String>> entry : headerFields){
        String key = entry.getKey();
        List<String> value = entry.getValue();
    }

 

(11)一组来自典型HTTP请求的相应字段头
Date: Wed, 27 Aug 2008 00:15:48 GMT
Server: Apache/2.2.2(Unix)
Last-Modified: Sun, 22 Jun 2008 20:53:38 GMT
Accept-Ranges: bytes
Content-Length: 4813
Connection: close
Content-Type: text/html

 

long getDate() : 返回 date 头字段的值,即创建日期。 
long getExpiration() : 返回 expires 头字段的值,即过期日。
long getLastModified() : 返回 last-modified 头字段的值,即最后一次被修改日期。
int getContentLength() : 返回 content-length 头字段的值,即如果知道内容的长度,则返回该长度,否则返回-1。
String getContentType() : 返回 content-type 头字段的值,即获取内容的类型,比如text/plain或image/gif。
String getContentEncoding() : 返回 content-encoding 头字段的值,即获取内容的编码,比如gzip,这个值不太常用,因为默认的identity编码并不是Content-Encoding头来设定的。


Java提供了6个方法用以访问大多数常用的消息头类型的值,并在需要的时候将它们转换成数字类型。其中返回类型为long的方法返回的是从格林威治时间1970年1月1日开始计算的秒数。
        用于访问响应头值的简便方法
键名                                方法名                         返回类型
Date                               getDate                         long       
Expires                          getExpiration                long
Last-Modified                getLastModified          long
Content-Length            getContentLength        int
Content-Type                getContentType            String
Content-Encoding        getContentEncoding   String

 

(12)一个常会遇到的问题是Java平台是否支持对安全Web页面的访问(https: URL) : 从Java SE 1.4开始,对安全套接字层ssl的支持已经成为标准程序库的一部分

 

 

3.提交表单数据
当表单数据被发送给Web服务器时,通常会有两个命令会被用到: GET 和 POST
(1)在使用GET命令时,只需将参数附在URL结尾处即可。
例: http://host/script?parameters
其中,每个参数都有"名字=值"的形式,而这些参数之间用&字符分隔开。
参数的值遵循的规则
    1)保留字符A-Z、a-z、0-9以及 . - * _
    2)用 + 字符替换所有的空格
    3)将其他所有字符编码为UTF-8,并将每个字节都编码为 % 后面紧跟着一个两位的十六进制数字。
        例如,发送街道名S. Main,可以使用S%2e+Main,因为十六进制2e是"."的ASCII码值。
      这种编码方式使得在任何中间程序中都不会混入空格,并且也不需要对其他特殊字符进行转换。
GET命令很简单,但是有一个重要的局限性,大多数浏览器都对GET请求中可以包含的字符数做了限制。
(2)使用POST命令时,并不需要在URL中添加任何参数,但是从URLConnection中获取输入流,并将名-值对写入该流中。当然,仍然需要对这些值进行URL编码,并用&字符将它们隔开。
POST提交数据流程
    1)创建一个URLConnection对象

    URL url = new URL("http://host/script");
    URLConnection connection = url.openConnection();

 

    2)调用setDoOutput方法建立一个用于输出的连接。

    connection.setDoOutput(true);

 

     3)调用getOutputStream方法获得一个流,可以通过这个流向服务器发送数据。
      如果要向服务器发送文本信息,那么可以将流包装在PrintWriter对象中。

    PrintWriter out = new PrintWriter(connection.getOutputStream());

 

    4)现在可以向服务器发送数据了

    out.print(name1 + "=" + URLEncoder.encode(value1, "UTF-8") + "&");
    out.print(name2 + "=" + URLEncoder.encode(value2, "UTF-8"));

 

    5)关闭输出流

    out.close();

 

    6)调用getInputStream方法读取服务器的响应。

例:通过POST向服务器发送信息,并接收返回数据。其中urlString为地址,nameValuePairs为表单数据

    public static String doPost(String urlString, Map<String, String> nameValuePairs) throws IOException{
        URL url = new URL(urlString);
        URLConnection connection = url.openConnection();
        //打开输出流
        connection.setDoOutput(true);
       
        PrintWriter out = new PrintWriter(connection.getOutputStream());
        boolean first = true;
        for(Map.Entry<String, String> pair : nameValuePairs.entrySet()){
            if(first){
                first = false;
            }else{
                out.print("&");
            }
            String name = pair.getKey();
            String value = pair.getValue();
            out.print(name);
            out.print("=");
            out.print(URLEncoder.encode(value, "UTF-8"));
        }
       
        out.close();
        Scanner in;
        StringBuilder response = new StringBuilder();
        try{
            in = new Scanner(connection.getInputStream());
        }catch(IOException e){
            if(!(connection instanceof HttpURLConnection)){
                throw e;
            }
            //捕获错误页面
            InputStream err = ((HttpURLConnection) connection).getErrorStream();
            if(err == null){
                throw e;
            }
            in = new Scanner(err);
        }
       
        while(in.hasNextLine()){
            response.append(in.nextLine());
            response.append("\n");
        }
        in.close();
        return response.toString();
    }
 
 


在读取响应时,如果服务器端运行错误,那么调用connection.getInputStream()时就会抛出FileNotFoundException异常,但是此时服务器会返回一个错误页面(常见的404)。
为了捕获错误页面,可以将URLConnection对象转换为HttpURLConnection类,并调用它的getErrorStream方法。

    InputStream err = ((HttpURLConnection) connection).getErrorStream();
 

(3)URLConnection向服务器发送的内容。
URLConnection对象先向服务器发送一个请求头,当提交表单数据时,该请求头必须包含
    Content-type: application/x-www-form-urlencoded
而POST的请求头还必须包括长度,例
    Content-Length: 24
所以URLConnection对象会把发送到输出流的所有数据都缓存起来,这是因为在发送之前首先确定内容的总长度。

 

DEMO

import java.awt.EventQueue;

import javax.swing.JFrame;

public class PostTest {
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable(){
			public void run(){
				JFrame frame = new PostTestFrame();
				frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				frame.setVisible(true);
			}
		});
	}
}

 

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.SwingWorker;

public class PostTestFrame extends JFrame {
	
	private JPanel northPanel;
	
	public PostTestFrame(){
		setTitle("PostTest");
		
		northPanel = new JPanel();
		add(northPanel, BorderLayout.NORTH);
		northPanel.setLayout(new GridLayout(0, 2));
		northPanel.add(new JLabel("Host: ", SwingConstants.TRAILING));
		final JTextField hostField = new JTextField();
		northPanel.add(hostField);
		northPanel.add(new JLabel("Action: ", SwingConstants.TRAILING));
		final JTextField actionField = new JTextField();
		northPanel.add(actionField);
		for(int i=1;i<=8;i++){
			northPanel.add(new JTextField());
		}
		
		final JTextArea result = new JTextArea(20, 40);
		add(new JScrollPane(result));
		
		JPanel southPanel = new JPanel();
		add(southPanel, BorderLayout.SOUTH);
		JButton addButton = new JButton("More");
		southPanel.add(addButton);
		addButton.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				northPanel.add(new JTextField());
				northPanel.add(new JTextField());
				pack();
			}
		});
		
		JButton getButton = new JButton("Get");
		southPanel.add(getButton);
		getButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				result.setText("");
				final Map<String, String> post = new HashMap<String, String>();
				for(int i=4;i<northPanel.getComponentCount();i+=2){
					String name = ((JTextField)northPanel.getComponent(i)).getText();
					if(name.length()>0){
						String value = ((JTextField)northPanel.getComponent(i + 1)).getText();
						post.put(name, value);
					}
				}
				new SwingWorker<Void, Void>(){
					protected Void doInBackground() throws Exception{
						try{
							String urlString = hostField.getText() + "/" + actionField.getText();
							result.setText(doPost(urlString, post));
						}catch(IOException e){
							result.setText("" + e);
						}
						return null;
					}
				}.execute();
			}
		});
		
		
	}
	
	public static String doPost(String urlString, Map<String, String> nameValuePairs) throws IOException{
		URL url = new URL(urlString);
		URLConnection connection = url.openConnection();
		connection.setDoOutput(true);
		
		PrintWriter out = new PrintWriter(connection.getOutputStream());
		boolean first = true;
		for(Map.Entry<String, String> pair : nameValuePairs.entrySet()){
			if(first){
				first = false;
			}else{
				out.print("&");
			}
			String name = pair.getKey();
			String value = pair.getValue();
			out.print(name);
			out.print("=");
			out.print(URLEncoder.encode(value, "UTF-8"));
		}
		
		out.close();
		Scanner in;
		StringBuilder response = new StringBuilder();
		try{
			in = new Scanner(connection.getInputStream());
		}catch(IOException e){
			if(!(connection instanceof HttpURLConnection)){
				throw e;
			}
			InputStream err = ((HttpURLConnection) connection).getErrorStream();
			if(err == null){
				throw e;
			}
			in = new Scanner(err);
		}
		
		while(in.hasNextLine()){
			response.append(in.nextLine());
			response.append("\n");
		}
		in.close();
		return response.toString();
	}
	
}
 

 

 

文章评论

一个程序员的时间管理
一个程序员的时间管理
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
程序员必看的十大电影
程序员必看的十大电影
鲜为人知的编程真相
鲜为人知的编程真相
如何成为一名黑客
如何成为一名黑客
每天工作4小时的程序员
每天工作4小时的程序员
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
Java程序员必看电影
Java程序员必看电影
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
程序员都该阅读的书
程序员都该阅读的书
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
编程语言是女人
编程语言是女人
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
总结2014中国互联网十大段子
总结2014中国互联网十大段子
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
代码女神横空出世
代码女神横空出世
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
 程序员的样子
程序员的样子
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
漫画:程序员的工作
漫画:程序员的工作
程序员的鄙视链
程序员的鄙视链
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
中美印日四国程序员比较
中美印日四国程序员比较
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
程序员应该关注的一些事儿
程序员应该关注的一些事儿
我的丈夫是个程序员
我的丈夫是个程序员
程序员和编码员之间的区别
程序员和编码员之间的区别
为什么程序员都是夜猫子
为什么程序员都是夜猫子
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
那些争议最大的编程观点
那些争议最大的编程观点
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
10个调试和排错的小建议
10个调试和排错的小建议
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有